闭包出错了——为什么匿名函数 return 最后

Closures gone awry - why does anonymous function return last

我正在阅读这篇文章 (#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 最后一行,这篇文章的这一部分指的是什么?究竟是哪里的外部变量被改变导致了这个问题?

函数有问题

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);

基本上可以归结为:

嵌套函数可以访问包含它的函数的范围。在您发布的示例中,变量 i 是 3 在 for 循环之后的父函数中。内部函数引用外部函数中的变量。

在本文的第二个示例(使用 IIFE)中,嵌套函数接受变量 i 作为函数 (IIFE) 的参数 j。在 JavaScript 中,数字按值传递,因此内部函数获取 i 当前值的副本,而不是对 i.

的引用

要理解的关键点是闭包捕获变量,而不是当前值。考虑例如:

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 稍后发生突变,它就不会受到影响。

闭包存储对外部函数变量的引用。

stalloneID.id 只是存储功能,但重要的是这里没有执行 yet.If 你登录你会看到下面的内容

console.log("inner: " + i);
return uniqueID + i; //this is accessing the i declared in 

外部函数,不在 For 循环中...

所以它仍然能够达到i.When的当前值你执行它(stalloneID.id())因为这个函数通过引用存储"i",它采用最新的值"i" 即 3

问题是由于闭包(inisde 函数)通过​​引用而不是值 访问外部函数中 i 的值

在这种特殊情况下,当 for 循环结束并且函数 returns 时,闭包使用 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