关闭错误 - 为什么匿名函数最后返回

时间:2015-01-29 20:58:11

标签: javascript closures

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

5 个答案:

答案 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