JS的三座大山之闭包(自己理解)

JS高级特性中闭包是一个特色。所谓闭包,本质就是在一个函数内部创建另一个函数。JS的百度解释:

闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

闭包的特性:

①函数嵌套函数(函数B嵌套在A的内部)(函数A返回函数B)
②函数内部可以引用函数外部的参数和变量(沿着作用域链寻找,先从自身开始查找,如果自身没有继续往上级查找,自身拥有直接调用,哪个近就用哪个)
③参数和变量不会被GC(垃圾回收机制)回收(延长内部变量的生命周期)
所谓垃圾回收机制就是在JS中,如果一个对象不再被引用,那么这个对象就会被GC回收。如果两个对象互相引用,不再被第三者引用,那么这两个互相引用的对象也会被回收。因为函数A被B引用,B又被A外的C引用,所以函数A执行后不会被回收。
JS的作用域分为全局和局部两种,js作用域中访问变量是由内向外的。内部作用域可以获得当前作用域下的变量并且可以获得当前包含当前作用域的外层作用域下的变量,不能由外向内。在不同的函数中作用域中也是不能相互访问彼此变量的。那么该怎么解决呢,啊哈闭包可以解决!
闭包由函数和创建该函数的环境构成。

闭包的缺点:

①滥用闭包会造成内存泄漏,因为闭包中引用到的包裹函数中定义的变量都永远不会被释放,所以我们应该在必要的时候及时的释放闭包函数。解决方法是在使用之后手动为它赋值为null
②闭包会跨域访问,导致性能损失,解决办法是把跨域作用域变量存储在局部变量中,然后直接访问局部变量减轻对执行速度的影响

闭包的优点:

①保护函数内的变量安全,实现封装,防止变量流入其他环境发生命名冲突
②在内存中维持一个变量,可以做缓存,但是实用多了会消耗内存
③匿名自执行函数(没有方法名的函数)可以减少内存消耗

面试题:

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

上面的代码会输出什么?怎么改动上述代码,使其依次输出1、2、3、4、5
程序会先输出一个5,之后每隔1000毫秒输出一个6
解释一下:setTimeout有两个参数,第一个是回调函数,第二个是毫秒数表示要执行回调函数所要延迟的时间。setTimeout会返回一个Id,就是定时器的Id,上边的代码创建了5个定时器,但是默认只返回最后一个Id,所以先输出一个5.
其次,setTimeout()函数要等执行完函数调用栈中的代码,然后立即调用定时器。因为定时器都被放在了一个队列中,等上下文的可执行代码运行完毕之后开始运行定时器。所以在定时器的方法执行的时候,变量i变成了6,所以输出6.
最后,如果想输出1,2,3,4,5,需要把每个定时器所访问的变量独立,用JS的闭包。修改代码为以下的:

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

使用闭包可以得到1、2、3、4、5的结果,原因是改变了i的作用域,但是把循环中的每个setTimeout都独立成一个作用域是不行的,在JavaScript中,每个函数是一个独立的作用域,但是“{}”是不能形成独立的作用域的。
在ES6中使用let关键字可以声明一个仅对当前的“{}”内部有作用的变量,结果是一样的:

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

再来看一个例子:

function a(){
	var i="dog";
	return function(){
		return i;
	}
}
var b=a();
console.log(b());

a()的返回值是一个匿名函数,函数在a()作用域内部,所以可以获得a()作用域下的变量i的值,并且将这个值作为返回值赋给全局作用域下的变量b,实现了在全局变量下取到局部变量的值的作用。

function name(){
	var num=1;
	return function(){
		var i=4;
		console.log(++i);
		console.log(++num);
	}
}
var name1=name();
name1()//5 2
name1()//5 3

以上的例子说明没有被垃圾回收。一般情况下,在函数name执行完之后就应该连同它的变量一起销毁,但是这个例子里,匿名函数作为num的返回值被赋值给了name1,这时候相当于name1=function(){var i=4…},匿名函数内部引用着name里的变量num所以变量num无法被销毁,而变量i是每次调用时新创建,每次name1执行完之后就把属于自己的变量连同自己一起销毁,最后只剩下num,内存消耗。

JS的三座大山之闭包(自己理解)_第1张图片在这段代码中,函数fn1作为参数传入立即执行函数中,在执行fn2(30)的时候,30作为参数传入fn1中,这时候if(x>num)中的num取的并不是立即执行函数中的num,而是创建函数的作用域中的num,这里函数创建的作用域是全局作用域下,所以num是全局作用域的值15,即30>15,打印30
参考文章

你可能感兴趣的:(JavaScript,js,javascript,内存泄漏,前端)