mdn上对闭包的讲解其实很清楚了。
今天看了一下《你不知道的JavaScript》,到闭包那一讲,发现自己之前其实并不是很懂。
闭包,百科上解释是能够读取其他函数内部变量的函数,拿书中的例子来说:
function foo() {
var a = 2;
function bar() {
console.log( a );
}
return bar;
}
var baz = foo();
baz(); // 2
按理bar只能在foo函数内部调用执行,foo()调用结束后,根据垃圾回收机制,foo函数应该被清除,但由于闭包的存在,简单来说就是因为函数内部又声明了一个函数,内部函数bar具有对外部函数foo的内部作用域的引用,这个引用的存在就形成了一个闭包,闭包并不是某一个特定的函数或变量,它是一个形式,一种设计原则。
作用域链的存在,使得函数内部可以访问外部的变量;
闭包的存在,使得函数外部也能访问函数内部的变量。
比较经典的闭包是for循环闭包:
for (var i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
}
即使setTimeout最后的参数为0,执行里面的函数时,取值i的时候会从上级寻找,这个时候for循环执行完,i变为了6。
本来是想让每次循环输入顺序数,但没有达到想要的效 果,因为每次循环都相当于声明了一个相同的函数,等到最后依次执行时,最后的函数覆盖了之前的,因为这一点就都会输出6,因此解决的一个方法就是创建一个闭包
加上一层闭包立即执行函数以后,function里面变量i每次都能从外层找到传入的i,而这个i每次循环的时候都传进去了
另外一种方法是将for中的var改为let,解释起来是JavaScript引擎循环过程中能够记住上一轮的值。每一次的let都形成了一个块级作用域,如下:
let i=0;
function(){
console.log(i)
}
由于let是块级作用域,大括号将i绑定在里面,每次往上寻找都能记住,所以结果会依次输出。
注:循环引用的问题可以用 标记清除 来解决
图片来自:https://juejin.im/post/5d0706a6f265da1bc23f77a9#heading-9
垃圾收集器运行时将所有的元素都标记,从根部出发将能触及到的变量进行清除,未被清除的变量将被回收