这篇,我们来看看新手在闭包问题上会犯那些典型的错误,毕竟由闭包所导致的 bug 往往很难被发现,因为它们总是表面上看起来一切正常。
让我们来看一个三次的循环操作,它在每次迭代中都会创建一个返回当前循环序号的新函数。该新函数会被添加到一个数组中,并最终返回。具体代码如下:
function F(){
var arr = [], i;
for( i = 0; i < 3; i++ ){
arr[i] = function(){
return i;
};
}
return arr;
};
//运行一下
var arr = F();
arr; // [ ƒ, ƒ , ƒ ]
arr[0](); //3
arr[1](); //3
arr[2](); //3显然,这不是我们想要的结果,那么问题出现在在哪呢?答案就在 i 身上,我们仔细看这段代码,表达式 arr[ i ] = function(){ return i; } ,被赋值的是一个匿名函数,更确切的说,是这个匿名函数的引用(这点很重要),而匿名函数中 return i 的 i 从哪来呢?它是从它的上一层作用域中来的(var i),但这个变量 i 是通过 for 循环来赋值的,当循环结束后, 变量 i 的最终值是 3 ,因此,当我们执行 F( ) 函数后,数组 arr 的三个元素,都是一个匿名函数的引用 ƒ ,所以,当我们再次调用 arr 中的三个元素分别去取值的时候,取到的都是这个变量 i 在 for 循环结束后的最终值 3 ,所以这三个元素(匿名函数)的取值,都指向了这一共同值。
那么,应该如何纠正这种行为呢?答案是换一种闭包形式:
function F(){
var arr = [], i;
for( i = 0; i < 3; i++ ){
arr[i] = (function(x){
return function(){
return x;
}
})(i);
};
return arr;
};
//调用一下
var arr = F();
arr[0](); //0
arr[1](); //1
arr[2](); //2在这里,我们不再直接创建一个返回 i 的函数了,而是将 i 传递给了另一个即时函数,在该函数中, i 就被赋值给了局部变量 x ,这样一来,每次迭代中的 x 就会拥有各自不同的值了。
或者,我们也可以定义一个“正常点的”内部(私有)函数(即不使用 即时函数)来实现相同功能:
function F(){
function binder(x){
return function(){
return x;
}
};
var arr = [], i;
for( i = 0; i < 3; i++ ){
arr[i] = binder(i);
}
return arr;
}这样,在每次迭代操作中,我们在中间函数内将 i 的值“本地化”,从而达到了我们的要求。
本文摘自《JavaScript面向对象编程指南》,分享的目的仅供个人学习和理解,如需转载请备注本文出处,谢谢!