闭包
简单来讲
闭包的定义其实很简单:函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包。
function A() {
let a = 1
window.B = function () {
console.log(a)
}
}
A()
B() // 1
很多人对于闭包的解释可能是函数嵌套了函数,然后返回一个函数。其实这个解释是不完整的,就比如上面这个例子就可以反驳这个观点。
在 JS 中,闭包存在的意义就是让我们可以间接访问函数内部的变量。
经典面试题,循环中使用闭包解决 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)
}
在上述代码中,我们首先使用了立即执行函数将 i 传入函数内部,这个时候值就被固定在了参数 j 上面不会改变,当下次执行 timer 这个闭包的时候,就可以使用外部函数的变量 j,从而达到目的。
第二种就是使用 setTimeout 的第三个参数,这个参数会被当成 timer 函数的参数传入。
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)
}
实际上
函数对象可以通过作用域链相互关联起来,函数体内部的变量可以保存在函数作用域内,这种特性在计算机科学中称为“闭包”。
从技术角度讲,所有的JavaScript函数都是闭包:它们都是对象,它们都关联到作用域链。定义大多数函数时的作用域链在调用函数时依然有效,这并不影响闭包。当调用函数时闭包所指向的作用域链和定义函数时的作用域链不是同一个作用域链时,就感觉非常微妙。当一个函数嵌套了另一个函数时,外部函数将嵌套的函数对象作为返回值返回的时候往往会发生。
通过代码来看看:
var scope="global scope";
function checkscope(){
var scope="local scope";
function f(){return scope}
return f();
}
chenkscope();
var scope="global scope";
function checkscope(){
var scope="local scope";
function f(){return scope}
return f;
}
chenkscope()();
上面两段代码,第一段输出“local scope”肯定没问题,那第二段中将函数内的一堆圆括号移到checkscope()之后。checkscope()现在只返回函数内嵌套的一个函数对象,而不是直接返回结果。在定义函数的作用域外面调用这个嵌套函数,结果依然是“local scope”,而不是“global scope”。JavaScript函数的执行用到了作用域链,这个作用域链是函数定义时创建的。嵌套的函数f()定义在这个作用域链里,其中的变量scope一定是局部变量,不管在何时何地执行函数f(),这种绑定在执行f()时依然有效,因此,最后一行代码返回“local scope”,而不是“global scope”。
再看看这个例子:
function counter(){
var n=0;
return {
count:function(){return n++;},
reset:function(){n=0;}
};
}
var c=counter(),d=counter();
c.count() //0
d.count() //0
c.reset()
c.count() //0
d.count() //1
上面的例子中,在同一个作用域链中定义了两个闭包,这两个闭包共享同样的私有变量或变量,但是要特别小心那些不希望共享的变量往往不经意间共享给了其他的闭包,看下面的代码:
//这个函数返回一个总是返回v的函数
function constfunc(v){return function(){return v;};}
//创建一个数组来存储常数函数
var funcs=[];
for(var i=0;i<10;i++){
funcs[i]=constfunc(i);
}
funcs[5]() //5
//返回一个函数组成的数组,他们的返回值是0~9
function constfuncs(){
var funcs=[];
for(var i=0;i<10;i++){
funcs[i]=function(){return i;};
}
return funcs;
}
var funcs=constfuncs();
funcs[5]() //10
第一段代码利用循环创建了很多闭包,每一个闭包都有自己的变量i;而在第二段代码中,创建了10个闭包,并将他们存储到数组中,这些闭包都是在同一函数中调用定义的,因此它们可以共享变量i。当constfuncs()返回时,变量i的值是10,所有闭包都共享这个值,因此,数组中的函数的返回值都是同一个值。
闭包中要注意this,this是JavaScript中的关键字而不是变量。每个函数调用都包含一个this值,如果外部函数里的闭包访问this,最好将this转存为一个变量以免this的不确定性带来的歧义
var self=this;//将this保存在一个变量中,以便嵌套的函数能够访问它