闭包就是一个函数。什么函数呢?是能访问另一个函数作用域的函数(可以读取其他函数内部变量的函数)。
由于在Javascript语言中,只有函数内部的子函数才能读取其父函数的局部变量,因此可以把闭包理解为**“定义在一个函数内部的函数”**
闭包主要有两个作用:
通过如下代码来理解:
function f1(){
var n=999;
nAdd=function(){n+=1}
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000
上述代码函数f1内部定义了一个函数f2,并将其作为返回值,则f2就是一个闭包。由于将f1的执行结果赋值给变量result,那么result实际上就是闭包f2函数。它一共执行了两次,第一次值是999,第二次值是1000.这证明了f1中局部变量n的值一直保存在内存中,并没有在f1调用后被自动消除。
为什么会这样呢?因为f2被赋给了一个全局变量,因此f2会始终在内存中,而f2依赖于f1,因此f1也会始终在内存中,不会在调用结束后被垃圾回收机制回收。
上述代码还有一个需要注意的地方在于nAdd=function(){n+=1},首先在nAdd前面没有关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数,而这个匿名函数也是一个闭包,因此可以通过nAdd对f1内的局部变量进行操作。
闭包中需要注意的是,返回的函数并没有立即执行,而是需要再次调用才会执行。
上述代码中,返回的函数f2并没有立即执行,而是调用了result()才执行的。
再看一段代码:
function count() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push(function () {
return i * i;
});
}
return arr;
}
var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];
上述代码,每次循环都创建一个函数,总共创建了3个函数,并将这3个函数放入数组arr中返回了。
你可能认为调用f1(),f2(),f3()的结果分别是:1,4,9,但实际结果是:
f1(); // 16
f2(); // 16
f3(); // 16
全部都是16!
我们分析一下上面代码的执行流程:
首先var results = count();
之后,函数count已经被调用了,所以依次执行函数内的各段代码:var arr = [];,for (var i=1; i<=3; i++)
,这个for循环尤其值得注意。因为此时循环体执行了push方法,将一个个函数function () { return i * i;}
添加到数组内,但是这个函数并没有被调用,还只是一个变量,所以for循环依次执行,直到i = 4。因为闭包,内部函数function () { return i * i;}
引用的i就是外部变量,for循环中的i = 4。所以,之后数组arr内的函数的i都是4。
调用函数count后,变量results已经是数组arr了。数组里面元素依次是function f1() { return i * i;} function f2() { return i * i;} function f3() { return i * i;}
。但是三个函数都没有被调用,直到var f1 = results[0];
,此时function f1() { return i * i;}
开始执行,如上段所写,此时的i = 4,所以,返回值就是16了。后面两个调用也是类似情况。
因此返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
如果一定要引用循环变量怎么办?
**解决方法一:**再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
function count() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push((function (n) { //立即执行函数
return function () {
return n * n;
}
})(i));
}
return arr;
}
var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];
f1(); // 1
f2(); // 4
f3(); // 9
上述代码创建了一个立即执行函数,将循环变量当前的值绑定给了立即执行函数,由于立即执行函数的存在数组里的值依次是function f1() { return 1 * 1;} function f2() { return 2 * 2;} function f3() { return 3 * 3;}
。从而达到了调用f1,f2,f3分别得到1,4,9的效果。
解决方法2:
将循环体中的var改成let
主要参考:
https://www.liaoxuefeng.com/wiki/1022910821149312/1023021250770016
https://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html