JavaScript闭包立即执行

7
考虑以下Javascript代码:

var a = [];

var f = function() {

    for (var i = 0; i < 3; i++) {
        a.push(function(){alert(i)});
    }
    for (var j = 0; j < 3; j++) {
        a[j]();
    }
};

警报每次都打印“3”。我希望有一个不同的行为——在循环的每次迭代中生成一个函数,该函数打印出i的当前值。也就是说,生成3个打印不同索引的函数。
有什么想法吗?

只是想补充一下,这是因为Javascript没有块级作用域的概念,只有函数作用域,这也让我感到困惑... http://www.mattfreeman.co.uk/2010/03/closures-scope-in-javascript-vs-c/ - user53791
5个回答

7
创建一个匿名函数,该函数接受i作为参数并返回该函数本身:
for (var i = 0; i < 3; i++) {
    a.push((function(i) {
        return function() {
            alert(i);
        }
    })(i));
}

for (var j = 0; j < 3; j++) {
    a[j]();
}

或者类似这样的操作:创建一个匿名函数,该函数接受 i 作为参数,将该函数添加到数组中:

for (var i = 0; i < 3; i++) {
    (function(i) {
        a.push(function() {
            alert(i);
        });
    })(i);
}

for (var j = 0; j < 3; j++) {
    a[j]();
}

1
虽然这不是必需的,但我认为将“立即执行”的函数包装在 () 中会使代码更加清晰,并更好地描述您的意图 -> (function(i){ ... })(i); - gnarf
@gnarf,我自己也在考虑这个问题。我想这确实可以使意图更清晰。我会进行编辑。 - strager
这似乎是通过提供一种不容易受到相同潜在缺陷影响的替代方案来回避原始问题...你在这里做的是将值推入数组。原帖作者正在推送函数,我们推测这些函数将在稍后执行... - Funka
抱歉,我评论得太快了。你是正确的。请查看我的答案,以获得同样问题的可比解决方案。 - Funka
1
@funka - 你仍然推函数:创建(function(i){会将变量i的作用域限制在函数被调用时保持不变。使用})(i);,你立即以i作为参数调用新函数,这将返回一个函数...实际上将[function(){alert(0);},function(){alert(1);},function(){alert(2);}]存储在a中。 - gnarf
是的,我在发布评论后就看到了。我们两个答案都在作用域内创建了新变量来保存当前迭代的“i”,这两种方法都解决了相同的根本问题。 - Funka

6

这是另一种方法,使用柯里化

var a = [];
var f = function() {
    for (var i = 0; i < 3; i++) {
        a.push((function(a){alert(a);}).curry(i));
    }
    for (var j = 0; j < 3; j++) {
        a[j]();
    }
};

// curry implementation
Function.prototype.curry = function() {
  var fn = this, args = Array.prototype.slice.call(arguments);
  return function() {
    return fn.apply(this, args.concat(
      Array.prototype.slice.call(arguments)));
  };
};

请检查上面的代码片段,可以在这里运行。


1
很好的使用了柯里化 - 尽管现在我有点饿了... - gnarf

1
var iterate = (function () {
    var i, j = [];
    for (i = 0; i < 3; i += 1) {
        j.push(i);
        alert(j[j.length - 1]);
    }
}());

你不需要闭包仅仅输出一个值。 但是,你的代码应该被包含在一个函数中进行面向对象的封装。 函数不需要调用即可执行。


函数不必被调用就可以执行。什么意思?这个陈述并不是很清楚,而且听起来是错误的。请澄清一下。 - strager
为了执行一个函数,必须发生以下三种情况之一:1)该函数必须由正在执行的其他东西通过名称调用。2)该函数可以被插入到方法中,此时该函数可以是匿名的并且仍然可以被执行。我强烈反对在不给函数命名的情况下使用它们。3)如果函数在其闭括号后面以括号结尾(例如}()),则函数可以完全自行执行,因为它们被解释为立即调用。这被称为立即调用。 - austin cheney
2
@austin:我非常不同意“强烈反对使用没有名称的函数”的说法——匿名函数是JavaScript忍者工具箱中最有用的工具之一。 - gnarf
@cheney,42'hello world'{ }new Array()function() { }都是会被计算出一个值的表达式。 - strager
@strager,这些可能会被评估为一个值,但这并不意味着它们是值。对函数执行typeof()操作,它将输出"function",这不是一个值类型。你可能需要复习一下JavaScript类型。http://www.mozilla.org/js/language/js20-2000-07/libraries/types.html - austin cheney
显示剩余5条评论

0
您可以将循环体放在匿名函数中:
var a = [];

for(var i = 0; i < 3; i++) (function(i) {
    a.push(function() { alert(i); });
})(i)

for(var j = 0; j < 3; j++) {
    a[j]();
}

通过创建该函数并将循环的“i”值作为参数传递,我们在循环体内创建了一个新的“i”变量,它实际上隐藏了外部的“i”。现在,您推入数组的闭包看到的是新变量,其值在第一次循环中调用外部函数时设置。如果我们在创建新变量时使用不同的名称,这可能会更清晰... 这样做也可以达到相同的效果:
var a = [];

for(var i = 0; i < 3; i++) (function(iNew) {
    a.push(function() { alert(iNew); });
})(i)

for(var j = 0; j < 3; j++) {
    a[j]();
}

“iNew” 的值被赋为0,然后是1,最后是2,因为该函数会立即被循环调用。

-2

function(i){alert(i)


2
很有可能,变量i未定义。 - strager
如果使用以下代码: a.push(function(i){alert(i)}); 而不是 a.push(function(){alert(i)}); 那么这将起作用。 - T. Markle

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接