2

我对这个 Closure 事情有些困惑。我在下面有两个单独的代码,它们看起来相似,但它们的输出不同。

function setup(x) {
var array = [];
for(var i=0;i<arguments.length;i++){
    array[i]= arguments[i];
}
return array;
}
console.log(setup('a','b'));  // will output ["a","b"] 

--------------
function f() {
var i, array = [];
for(i = 0; i < 3; i++) {
    array[i] = function(){
        return i;
    }
}
return array;
}

var a = f();                 
console.log(a());       //output: [function(),function(),function()]
console.log(a[0]());    //output: 3 //same output in a[1]() and a[2]() calls as well

现在我的问题是,为什么输出不同?上面的两个代码都返回一个数组。在第一个代码中,它正确打印数组中的所有元素,而在第二个代码中,为什么不打印 [1,2,3]?

4

2 回答 2

4

在您的第二个示例中,您正在3循环中创建函数,但所有函数都在同一个变量范围内创建,因此它们都引用并返回同一个i变量的值。

因此,i函数返回的值代表函数被调用时的值。因为您在循环之后i调用它们,所以值为is 3,所以这就是返回的值。

这就是闭包的意思。这些函数“关闭”了存在于创建它们的变量范围中的变量。它们不会关闭变量的,而是关闭变量本身,因此它们始终获取变量的当前状态。

对于要引用 的不同值的i每个函数,需要在具有自己唯一性的单独变量范围中创建每个函数i

因为在 JavaScript 中创建新变量范围的唯一方法是调用函数,所以您需要在新函数调用中创建每个函数。

function makeFunction(j) {
    return function(){
        return j;
    };
}

function f() {
    var i, array = [];
    for(i = 0; i < 3; i++) {
        array[i] = makeFunction(i);
    }
    return array;
}

所以在这里我做了一个新的函数,叫做makeFunction. 它接收一个参数,并返回一个引用并返回该参数的新函数。

因为每次调用makeFunction都会创建一个新的唯一变量范围,所以返回的每个函数都将引用其自己的唯一j变量,因此将返回被调用j时存在的值(除非您的函数修改了 ,如果您愿意,它可以这样做)makeFunctionj

请注意,j为了清楚起见,我使用了变量名称。您也可以使用i,或其他名称。

于 2012-11-03T00:43:02.673 回答
0

在 JavaScript 中,您有函数语句和函数表达式。第一个声明命名函数,后者评估为命名或匿名函数。您正在使用函数表达式。

我认为您想要做的是调用表达式。您所要做的就是立即调用返回的函数i

function f() {
    var i, array = [];
    for (i = 0; i < 3; i++) {
        // The extra parentheses around the function are unnecessary here.
        // But this is more idiomatic, as it shares syntax with how function
        // expressions are introduced in statements.
        // I.e. you could just copy-paste it anywhere.
        array[i] = (function () {
            return i;
        })(); // don't rely on automatic semicolon insertion
    }
    return array;
}

另请注意,在您有问题的示例中,所有闭包都返回3,因为它们都捕获相同的 variable i

编辑:为了使上一段更清楚,如果你真的想要有 3 个不同的闭包,你必须为每个闭包创建一个新的范围。与其他语言不同,Javascript 不会仅仅通过打开一个块来创建范围。它只在函数中创建范围。这里有两种方法:

function f() {
    var i, array = [];
    for (i = 0; i < 3; i++) {
        // With a parameter in an inner function
        array[i] = (function (n) {
            return function () {
                // A new n is captured every time
                return n;
            };
        })(i);
    }
    return array;
}

function f() {
    var i, array = [];
    for (i = 0; i < 3; i++) {
        array[i] = (function () {
            // With a variable in an inner function
            var n = i;
            return function () {
                // A new n is captured every time
                return n;
            };
        })();
    }
    return array;
}

然而,下一个例子是错误的,因为即使nfor块中声明,它也好像它已在函数顶部声明并且仅在for块中初始化。请记住,这是 JavaScript,不是 Java,不是 C,不是 C#,不是 <whatever 括号内的语言>:

function f() {
    var i, array = [];
    for (i = 0; i < 3; i++) {
        var n = i;
        array[i] = function () {
            return n;
        };
    }
    return array;
}

等效代码:

function f() {
    var i, array = [];
    var n;
    for (i = 0; i < 3; i++) {
        n = i;
        array[i] = function () {
            return n;
        };
    }
    return array;
}
于 2012-11-03T00:50:40.027 回答