我正在阅读这篇文章(#3)on JS Closures,并试图理解这一点,当外部函数的变量用for循环改变时,闭包会出错...
function celebIDCreator (theCelebs) {
var i;
var uniqueID = 100;
for (i = 0; i < theCelebs.length; i++) {
console.log("outer: " + i);
//this anon function returns after the loop has finished
theCelebs[i]["id"] = function () {
console.log("inner: " + i);
return uniqueID + i; //this is accessing the i declared in outer function, not in For loop...
}
}
console.log("outside for loop: " + i);
return theCelebs;
}
var celebs = [{name : "Stallone", id: 0}, {name : "Cruise", id : 0}, {name : "Willis", id : 0}];
var create = celebIDCreator (celebs);
var stalloneID = create[0];
提供输出:
outer: 0
outer: 1
outer: 2
outside for loop: 3
inner: 3
我很困惑为什么内部匿名函数的输出是最后一行的console.logged,以及文章的这一部分是指什么?外部变量究竟在哪里被改变导致问题?
答案 0 :(得分:2)
要理解的关键点是闭包会捕获变量,而不是当前值。考虑例如:
function f() {
var i = 1;
return [function() { return ++i; },
function() { return i*2; }];
}
这两个闭包是共享相同的变量i
,所以你得到这种行为:
x = f();
console.log(x[0]()); // ==> 2
console.log(x[1]()); // ==> 4
console.log(x[0]()); // ==> 3
console.log(x[1]()); // ==> 6
这意味着如果你只是在一个循环中创建闭包,它们都捕获相同的变量,并且该值将是退出循环后的值。
常见的解决方案是使用
return (function(i){return function(){return ++i;}})(i);
相当于
return (function(i2){return function(){return ++i2;}})(i);
换句话说,闭包不再捕获外部i
变量,而是捕获中间函数的i2
参数(在每次迭代时创建的另一个变量)。通过这个技巧,您基本上可以创建一个捕获i
的当前值的闭包,并且在i
稍后发生变异时不会受到影响。
答案 1 :(得分:1)
问题在于功能
theCelebs[i]["id"] = function () {
console.log("inner: " + i);
return uniqueID + i; //this is accessing the i declared in outer function, not in For loop...
}
函数内部i
的值引用了function celebIDCreator
的词汇环境和匿名function(){
的变量环境。由于变量环境中没有定义,因此必须从词法环境中获取i
。此外,由于函数是在以后调用的,当从词法环境中取i
时,它已被for
循环修改,现在是最终值(3)。
替代方案包括使用IIFE来关闭i
值。
theCelebs[i]["id"] = (function (i) {
return function(){
console.log("inner: " + i);
return uniqueID + i; //this is now accessing the i declared in the IIFE
}
})(i);
答案 2 :(得分:1)
基本上归结为:
嵌套函数可以访问包含它的函数的作用域。在for循环后,在父函数中发布变量i
的示例为3。内部函数引用外部函数中的变量。
在本文的第二个示例中(使用IIFE),嵌套函数接受变量i
作为参数j
到函数(IIFE)。在JavaScript中,数字按值传递,因此内部函数获取当前值i
的副本,而不是i
的引用。
答案 3 :(得分:0)
Closures存储对外部函数变量的引用。
stalloneID.id只存储功能,但重要因素是它尚未执行。如果你记录,你会看到下面的
console.log("inner: " + i);
return uniqueID + i; //this is accessing the i declared in
外部函数,而不是For循环......
所以它仍然能够达到i的当前值。当你执行它(stalloneID.id())因为这个函数通过引用存储“i”时,它取最新值“i”为3
答案 4 :(得分:0)
问题是由于闭包(inisde函数)通过引用而不是值访问外部函数中的i的值。
在这种特殊情况下,当for循环结束并且函数返回时,闭包使用更新的i(3)值,因此返回103.
我们可以使用立即调用函数表达式(IIFE)来解决此问题:
for(i=0;i<theCelebs.length;i++)
{
theCelebs[0].id=function(j) // j=i , passed on invocation
{
return function()
{
return uniqueId+j;//in each loop , correct value of j(i) is
//saved in array
}
}() // IIFE so that value is returned immediately
}(i) // IIFE with current value of i as parameter to closure