闭包经典问题
闭包经典问题如下:
function test() {
var arr = [];
for(var i = 0; i < 10; i++){
arr[i] = function () {
console.log(i + " ");
}
}
return arr;
}
var myArr = test();
for(var j = 0; j < 10; j++) {
myArr[j]();
}
本意是想打印1到10,却打印了10个10,为什么呢?详细解释过程如下:
- test函数预编译:
test-AO: {
arr: undefined,
i: undefined
}
- test函数执行:赋值后,进入循环语句,也是赋值,因此将10个内容相同的函数体赋值给arr数组,在循环外层加一个打印语句可以看出:arr的值为
但此时匿名函数的作用域链为:
[[scope]]---->scopechain[0]---->test-AO;
---->scopechain[1]---->GO;
最后,将arr返回。
- 最后,到全局环境中循环执行arr中的各个函数,此时,匿名函数的作用域链为:
[[scope]] ---->scopechain[0]---->AO;
---->scopechain[1]---->test-AO;
---->scopechain[2]---->GO;
首先在自身AO寻找i,未找到,则到下一层,test-AO寻找i,但此时由于10次循环都已经执行完,test-AO中i为10,因此打印了10个10。
解决办法
上面打印10个10的原因是i并没有个性化地保存在函数的作用域链中,因此,我们的目的就是将其保存在作用域链中。
采用立即执行函数来解决这个问题。先简单介绍一下立即执行函数:
- 函数会立即执行,执行完后就被销毁;
- 一般针对初始化功能的函数,比如计算某些值,不需要再第二次使用的函数。
- 两种写法:
(function (b) {} (a)); //外层括号将其转化为表达式,因此可以被执行 (function (b) {})(a); // 注意:()为执行符号,只有表达式才能被执行符号执行
因此上面的例子可以改写为:
function test() {
var arr = [];
for(var i = 0; i < 10; i++) {
(function (j) {
arr[j] = function () {
console.log(j + " ");
}
} (i));
}
return arr;
}
var myArr = test();
for(var j = 0; j < 10; j++) {
myArr[j]();
}
可以看出,在原来的基础上,在arr赋值语句外层包了一个立即执行函数,这个函数的目的就是将i的值保留在function () {console.log(j + " ");}的作用域链中,我们从头来解析一下这个函数:
- 执行test函数:进入循环后,执行立即执行函数
当i为0时,传给j,此时a[0]仍被赋值为函数体function () {console.log(j + " ");},但这个时候,该函数的作用域链为:
[[scope]] ---->scopechain[0]---->立即执行函数的AO;
---->scopechain[1]---->test-AO;
---->scopechain[2]---->GO;
此时,立即执行函数的AO为:
AO:{
j: 0
}
当i为1时,传给j,此时a[1]仍被赋值为函数体function () {console.log(j + " ");},同样,该函数的作用域链为:
[[scope]] ---->scopechain[0]---->立即执行函数的AO;
---->scopechain[1]---->test-AO;
---->scopechain[2]---->GO;
此时,立即执行函数的AO为:
AO:{
j: 1
}
注:即使执行同一个函数,每次执行均会产生不同的AO。
依此类推,可以看出,当执行数组里面的每个函数时,会到其作用域链自顶向下查找,从而可以打印出0-9.