5.1 什么是闭包
当函数可以记住并访问定义时所在的(上下文)词法作用域时,就产生了闭包,即使函数是在定义时的词法作用域之外执行
//全局作用域
function foo() {//作用域1
var a = 2;
function bar() {
console.log(a);
}
return bar;
}
var fun = foo();//记住了函数定义时候的作用域1、全局作用域
fun(); //2————这就是闭包
闭包发生在定义时。(只有调用了外部函数生成了内部作用域才能生成闭包,这点很重要)
当运行了var fun=foo();时,发生了闭包。
fun记住了此时作用域1、全局作用域
记住全局作用域其实意义不大,因为全局作用域只有一个,但每运行一次foo(),都会生成一个新的作用域1。
因为当运行完一次foo()时,JavaScript引擎都会销毁foo()的整个内部作用域,再次运行foo()时,又会生成一个新的内部作用域。
而发生闭包后,内部作用域并不会消失,因为fun,或者说bar()依然在使用在该内部作用域。
//全局作用域
function a(){//作用域1
function b(){
}
return b;
}
var fun1=a();
var fun2=a();
console.log(fun1===fun2);//false
第一次调用a()时生成了一个作用域,第二次调用a()时又生成了一个全新的作用域,即使没有对内部作用域进行任何修改,fun1和fun2闭包记住的作用域也完全是两个作用域。这就是闭包的效果。
//全局作用域
function a(){
return a;
}
var fun1=a();
var fun2=a();
console.log(fun1===fun2);//true
而在这个例子中,fun1和fun2所记住的都是全局作用域,因此fun1===fun2。
5.2 循环与闭包
为了更好的说明闭包,循环是个最常见的例子
(注:延迟函数的回调会做循环结束时才执行,即使执行的是setTimeout(...,0) ,但是定义发生在循坏过程中,因此定义时记下的作用域和执行时的作用域完全不同。)
这个函数在定义的词法作用域以外的地方被引用。闭包使得函数可以继续访问定义时的词法作用域。
//全局作用域
for(let i = 1; i <= 5; i++) {//作用域2
(function() {//作用域1
setTimeout(function timer() {
console.log(i);
}, 0);
})();
}
由于使用的是let进行定义,因此i作为for循环的块作用域中的变量
for循环每个迭代中,定义的timer函数都记录下了有着不同的i值的作用域2
因此在定义的词法作用域以外的地方被引用时,输出的是不同的作用域2中的i值(1、2、3、4、5)。
//全局作用域
for(var i = 1; i <= 5; i++) {
(function() {//作用域1
setTimeout(function timer() {
console.log(i);
}, 0);
})();
}
而如果使用var 进行定义,事实上i会被绑定到全局作用域
for循环的迭代中,定义的timer函数记录的是相同全局作用域中的i
因此在定义的词法作用域以外的地方被调用时,输出的是相同的全局作用域中的i(此时i循环后值为6),因此输出结果是5个6。