关于闭包
---恢复内容开始---
什么是闭包?一句话解释就是:当函数可以记住并访问所在的词法作用域,
即使函数是在当前此法作用域之外执行,这是就产生了闭包。----------《你不知道的JavaScript》
请看下面一个例子:
var fn;
var a =1;
function foo(){
var a = 2;
function baz(){
console.log(a);
}
fn=baz;
}
function bar(){
fn();
}
foo();
bar(); //2
这段代码中,foo函数中 定义了baz,并将其引用赋值给全局变量fn,然后在bar中调用fn,最后执行结果为2而不是1,
所以就印证了上边的那句话,也就是说无论通过何种手段将内部函数传递到所在的词法作用域以外,他都会持有对原始定
义作用域的引用,无论和实质性这个函数都会使用这个闭包。或者简单来讲:函数所处的作用域是在定义时确定的,而不
是执行时。在这段代码中,fn是在全局中声明,在foo中定义(也就是将其指向baz函数,而baz的作用域是foo),然后又
在bar中调用的,所以最终他的作用域就是foo,它所访问的a变量也是foo中的a,所以输出是2.。这解释没毛病吧!
其实我们比较常见的闭包形式是将函数作为一个返回值return出去,比如下面这段代码:
function foo(){
var a = 2;
function bar (){
console.log(a)
}return bar
}
var baz=foo();
baz(); // 2 -------------这就是闭包的结果
在foo执行后,它的返回值赋值给baz,但是要注意函数(对象)的赋值只是将baz定义为指向函数的引用。
事实上,我们知道函数在调用结束之后,引擎的垃圾回收机制会释放不再使用的内存空间。所以在foo调用结束之后,按道理
其空间会被回收,但是神奇的是闭包可以阻止这一行为,所以其内部作用域依然存在,这是使用内部作用域的就是bar()
还有一种情况发生在异步函数调用过程中,比如setTimeout、事件监听函数、ajax等过程中,比如下列代码:
function wait(message){
setTimeout(function timer(){
console.log(message);
},1000)
}
wait('Hello Would!')
在等待1000ms这个过程中,实际上函数内部作用域并不会消失,timer函数会一直持有对wait作用域的的引用;
在事件监听、ajax等其他异步任务中,只要使用了回调函数,实际上都是在使用闭包!也就是说在等待触发条件的过程中,
回调函数依然持有定义它的作用域的引用;
所以你现在可以找一下你以前写的代码是不是不经意间就使用了闭包。
接下来还有一个常见的问题:循环和闭包
有这么一串代码,也许你之前就遇到过
for(var i=1;i<=5,i++){
setTimeout(function timer(){
console.log(i)
},1000*i)
}
在我们没有接触闭包和异步之前可能会认为这会连续输出1~5,每秒一个,但事实上不是。最终结果是5个6,也就是说i的自
增结束了之后才执行的timer函数,i自增结束之后其值已经变成了6,而for循环执行了5次setTimeout,产生了5个timer函数,
也就是闭包函数,这5个timer函数引用的是同一作用域(实际上是全局作用域),因为在es5标准下的for循环并不会产生局部作用域,
所以5个timer函数访问的 i 其实是一个 i ,所以输出5个6,至于为什么自增结束之后才执行timer,就要讲到异步了,这里先不讲异步。
接下来我们看解决方案。
解决的办法就是将每次for循环的 i 变量与本次setTimeout产生的timer函数绑定在独立的作用域当中,这样每个timer函数有自己的对应
的 i 变量了,我们可以使用立即执行函数来解决:
for(var i=1;i<=5;i++){
(function(){setTimeout(function timer (){
console.log(i)
},1000*i )
})()
}
这样就行了吗?当然不行,虽然产生了局部作用域,但是 i 变量还没有绑定到相应的局部作用域。所以要改一下
for(var i=1;i<=5;i++){
(function(){var a = i;
setTimeout(function timer (){
console.log(a )
},1000*i )
})( )
}
或者更美观一点
for(var i=1;i<=5;i++){
(function(a){setTimeout(function timer (){
console.log(a )
},1000*i )
})( i )
}
其实在ES6标准下就不需要这么麻烦了,只需一个let就完事。请看:
for(let i=1;i<=5;i++){
setTimeout(function timer (){
console.log(a )
},1000*i )
}
只是把var改成了let的声明方式。因为ES6新增了一个重要的特性:局部作用域
好困,明天再写!