JS 闭包(内存溢出与内存泄漏)(垃圾回收机制)

1.有关闭包定义javascript

闭包是指有权访问另外一个函数做用域中变量的函数,建立闭包的最多见的
方式就是在一个函数内建立另外一个函数,经过另外一个函数访问这个函数的局部变量

闭包的特性:
    函数内再嵌套函数
    内部函数能够引用外层的参数和变量
    参数和变量不会被垃圾回收机制回收

说说你对闭包的理解html

使用闭包主要是为了设计私有的方法和变量。闭包的优势是能够避免全局变量的污染,
缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易形成内存泄露。在js中,
函数即闭包,只有函数才会产生做用域的概念

闭包 的最大用处有两个,一个是能够读取函数内部的变量,另外一个就是让这些
变量始终保持在内存中

闭包的另外一个用处,是封装对象的私有属性和私有方法

好处:可以实现封装和缓存等;

坏处:就是消耗内存、不正当使用会形成内存溢出的问题

使用闭包的注意点java

因为闭包会使得函数中的变量都被保存在内存中,内存消耗很大,因此不能滥用
闭包,不然会形成网页的性能问题,在IE中可能致使内存泄露

解决方法是,在退出函数以前,将不使用的局部变量所有删除

闭包的定义其实很简单:函数 A 内部有一个函数 B,函数 B 能够访问到函数 A 中的变量,那么函数 B 就是闭包

function A() {
  let a = 1
  window.B = function () {
      console.log(a)
  }
}
A()
B() // 1

闭包会产生一个很经典的问题:面试

多个子函数的[[scope]]都是同时指向父级,是彻底共享的。所以当父级的
变量对象被修改时,全部子函数都受到影响。

解决:数组

变量能够经过 函数参数的形式 传入,避免使用默认的[[scope]]向上查找
使用setTimeout包裹,经过第三个参数传入
使用 块级做用域,让变量成为本身上下文的属性,避免共享

2.闭包简单例子
指的是有权访问另外一个函数做用域中变量的函数,
建立闭包的常见方式,就是在一个函数内部建立另外一个函数。浏览器

 function f1(){
    var n=999;
    function f2(){
      alert(n); // 999
    }
  }
function f1(){
    var n=999;
    function f2(){
      alert(n); 
    }
    return f2;
  }
  var result=f1();
  result(); // 999

3.闭包的用处:缓存

闭包能够用在许多地方。它的最大用处有两个,一个是前面提到的能够读取函数内部的变量,另外一个就是让这些变量的值始终保持在内存中。闭包

function f1(){
    var n=999;
    nAdd=function(){n+=1}
    function f2(){
      alert(n);
    }
    return f2;
  }
  var result=f1();
  result(); // 999
  nAdd();
  result(); // 1000

4.使用必闭包的问题:异步

因为闭包会使得函数中的变量都被保存在内存中,内存消耗很大,因此不能滥用闭包,不然会形成网页的性能问题。函数

闭包的例子:

function outerFun()
{
 var a=0;
 function innerFun()
 {
  a++;
  alert(a);
 }
 return innerFun;  //注意这里
}
var obj=outerFun();
obj();  //结果为1
obj();  //结果为2
var obj2=outerFun();
obj2();  //结果为1
obj2();  //结果为2
function outerFun()
{
 //没有var
 a =0;
 alert(a);  
}
var a=4;
outerFun();
alert(a);
结果为 0,0 真是奇怪,为何呢?

做用域链是描述一种路径的术语,沿着该路径能够肯定变量的值 .当执行a=0时,因
为没有使用var关键字,所以赋值操做会沿着做用域链到var a=4;  并改变其值.

5.闭包内的微观世界
参考学习:https://www.cnblogs.com/goloving/p/7062212.html
  若是要更加深刻的了解闭包以及函数a和嵌套函数b的关系,咱们须要引入另外几个概念:函数的执行环境(excution context)、活动对象(call object)、做用域(scope)、做用域链(scope chain)。以函数a从定义到执行的过程为例阐述这几个概念。

1.当定义函数a的时候,js解释器会将函数a的做用域链(scope chain)设置为定义a时a所在的“环境”,若是a是一个全局函数,则scope chain中只有window对象。
当执行函数a的时候,a会进入相应的执行环境(excution context)。
2.在建立执行环境的过程当中,首先会为a添加一个scope属性,即a的做用域,其值就为第1步中的scope chain。即a.scope=a的做用域链。
3.而后执行环境会建立一个活动对象(call object)。活动对象也是一个拥有属性的对象,但它不具备原型并且不能经过Javascript代码直接访问。建立完活动对象后,把活动对象添加到a的做用域链的最顶端。此时a的做用域链包含了两个对象:a的活动对象和window对象。
4.下一步是在活动对象上添加一个arguments属性,它保存着调用函数a时所传递的参数。
5.最后把全部函数a的形参和内部的函数b的引用也添加到a的活动对象上。在这一步中,完成了函数b的的定义,所以如同第3步,函数b的做用域链被设置为b所被定义的环境,即a的做用域。

当在函数b中访问一个变量的时候,搜索顺序是:

先搜索自身的活动对象,若是存在则返回,若是不存在将继续搜索函数a的活动对象,
依次查找,直到找到为止。
若是函数b存在prototype原型对象,则在查找完自身的活动对象后先查找自身的原型
对象,再继续查找。这就是Javascript中的变量查找机制。
若是整个做用域链上都没法找到,则返回undefined。

函数的定义与执行。文中提到函数的做用域是在定义函数时候就已经肯定,而不是在执行的时候肯定

6.有关闭包经典案例
经典面试题,循环中使用闭包解决 var 定义函数的问题

for ( var i=1; i<=5; i++) {
	setTimeout( function timer() {
		console.log( i );
	}, i*1000 );
}

首先由于 setTimeout 是个异步函数,全部会先把循环所有执行完毕,这时候 i 就是 6 了,因此会输出一堆 6。
解决办法两种,第一种使用闭包

for (var i = 1; i <= 5; i++) {
  (function(j) {
    setTimeout(function timer() {
      console.log(j);
    }, j * 1000);
  })(i);
}

第二种就是使用 setTimeout 的第三个参数

for ( var i=1; i<=5; i++) {
	setTimeout( function timer(j) {
		console.log( j );
	}, i*1000, i);
}

第三种就是使用 let 定义 i 了

for ( let i=1; i<=5; i++) {
	setTimeout( function timer() {
		console.log( i );
	}, i*1000 );
}

有关内存溢出与内存泄漏

1. 内存溢出
  * 一种程序运行出现的错误
  * 当程序运行须要的内存超过了剩余的内存时, 就出抛出内存溢出的错误
2. 内存泄露
  * 占用的内存没有及时释放
  * 内存泄露积累多了就容易致使内存溢出
  * 常见的内存泄露:
    * 意外的全局变量
    * 没有及时清理的计时器或回调函数
    * 闭包
// 1. 内存溢出
  var obj = {}
  for (var i = 0; i < 10000; i++) {
    obj[i] = new Array(10000000)
    console.log('-----')
  }

  // 2. 内存泄露
    // 意外的全局变量
  function fn() {
    a = new Array(10000000)
    console.log(a)
  }
  fn()

   // 没有及时清理的计时器或回调函数
  var intervalId = setInterval(function () { //启动循环定时器后不清理
    console.log('----')
  }, 1000)

  // clearInterval(intervalId)

    // 闭包
  function fn1() {
    var a = 4
    function fn2() {
      console.log(++a)
    }
    return fn2
  }
  var f = fn1()
  f()

  // f = null

7.js垃圾回收机制
转载:https://www.cnblogs.com/zhwl/p/4664604.html
因为字符串、对象和数组没有固定大小,当他们的大小已知时,才能对他们进行动态的存储分配。JavaScript程序每次建立字符串、数组或对象时,解释器都必须分配内存来存储那个实体。只要像这样动态地分配了内存,最终都要释放这些内存以便他们可以被再用,不然,JavaScript的解释器将会消耗完系统中全部可用的内存,形成系统崩溃。

如今各大浏览器一般用采用的垃圾回收有两种方法:标记清除、引用计数
标记清除
这是javascript中最经常使用的垃圾回收方式。当变量进入执行环境是,就标记这个变量为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,由于只要执行流进入相应的环境,就可能会用到他们。当变量离开环境时,则将其标记为“离开环境”。
引用计数  另外一种不太常见的垃圾回收策略是引用计数。引用计数的含义是跟踪记录每一个值被引用的次数。当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是1。相反,若是包含对这个值引用的变量又取得了另一个值,则这个值的引用次数就减1。当这个引用次数变成0时,则说明没有办法再访问这个值了,于是就能够将其所占的内存空间给收回来。这样,垃圾收集器下次再运行时,它就会释放那些引用次数为0的值所占的内存。

相关文章
相关标签/搜索