JavaScript 闭包 vs 匿名函数

581

我和我的一位朋友正在讨论JS中的闭包是什么以及它不是什么。我们只是想确保我们真正理解它。

让我们拿这个例子来说。我们有一个计数循环,希望延迟一段时间后将计数器变量打印在控制台上。因此,我们使用setTimeout闭包来捕获计数器变量的值,以确保它不会打印N次值为N的值。

没有闭包或与闭包相似的任何东西的错误解决方案如下:

for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
}

循环结束后,它将打印i的值10次,即10。

所以他的尝试是:

for(var i = 0; i < 10; i++) {
    (function(){
        var i2 = i;
        setTimeout(function(){
            console.log(i2);
        }, 1000)
    })();
}

按预期打印0到9。

我告诉他,他没有使用闭包来捕获i,但他坚持说他使用了。我通过将for循环体放在另一个setTimeout中(将他的匿名函数传递给setTimeout),再次打印10次10来证明他没有使用闭包。如果我将他的函数存储在一个var中,并在循环之后执行它,也会打印10次10。因此,我的论点是他实际上并没有真正“捕获”i的值,使他的版本不是一个闭包。

我的尝试是:

for(var i = 0; i < 10; i++) {
    setTimeout((function(i2){
        return function() {
            console.log(i2);
        }
    })(i), 1000);
}

我捕获了i(在闭包中命名为i2),然后现在我返回另一个函数并传递它。 在我的情况下,传递给setTimeout的函数真正捕获了i

现在谁在使用闭包,谁不是?

请注意,两个解决方案都会延迟地将0到9打印到控制台,因此它们解决了原始问题,但我们想要理解这两个解决方案中哪个使用闭包来完成这一点。


2
我们刚刚达成了一项协议:正确的那个人将获得与此问题相关的SO积分。 - brillout
1
@leemes - 你们两个都在使用闭包。你们都创建了两个函数 - 一个外部函数和一个内部函数;而且你们两个的内部函数都是闭包。你们所有的函数都是lambda表达式(匿名函数)。请查看我的答案获取详细信息。 - Aadit M Shah
1
@blesh - 我不知道什么是修改的闭包。我看到你的链接指向C#代码。JavaScript支持修改的闭包吗? - Aadit M Shah
1
修改闭包是一个有点不准确的名称。基本上,OP的第一个示例说明了一个修改过的闭包。这将是您具有一些自由变量,在某个内部方法访问之前被改变的情况。例如,从“for”循环中访问“i”,在第一个示例中在“setTimeout”内部访问。它会在任何超时回调执行之前被修改为10。 - Ben Lesh
显示剩余17条评论
12个回答

4

Consider the following. This creates and recreates a function f that closes on i, but different ones!:

i=100;

f=function(i){return function(){return ++i}}(0);
alert([f,f(),f(),f(),f(),f(),f(),f(),f(),f(),f()].join('\n\n'));

f=function(i){return new Function('return ++i')}(0);        /*  function declarations ~= expressions! */
alert([f,f(),f(),f(),f(),f(),f(),f(),f(),f(),f()].join('\n\n'));

while the following closes on "a" function "itself"
( themselves! the snippet after this uses a single referent f )

for(var i = 0; i < 10; i++) {
    setTimeout( new Function('console.log('+i+')'),  1000 );
}

或者更明确地说:

for(var i = 0; i < 10; i++) {
    console.log(    f = new Function( 'console.log('+i+')' )    );
    setTimeout( f,  1000 );
}

注意!在打印0之前,f的最后一个定义是function(){ console.log(9) }

警告!闭包概念可能会使初级编程的本质变得模糊。

for(var i = 0; i < 10; i++) {     setTimeout( 'console.log('+i+')',  1000 );      }

x-refs.:
JavaScript 闭包是如何工作的?
JavaScript 闭包解释
(JS)闭包是否需要在函数内部定义?
如何理解 JavaScript 中的闭包?
JavaScript 局部变量和全局变量混淆


代码片段第一次尝试 - 不确定如何控制 - 只需要“运行” - 不确定如何删除“复制”。 - ekim

-1

我想分享有关闭包的示例和解释。我制作了一个Python示例,还有两个图表来展示堆栈状态。

def maker(a, b, n):
    margin_top = 2
    padding = 4
    def message(msg):
        print('\n’ * margin_top, a * n, 
            ' ‘ * padding, msg, ' ‘ * padding, b * n)
    return message

f = maker('*', '#', 5)
g = maker('', '♥’, 3)
…
f('hello')
g(‘good bye!')

此代码的输出如下:

*****      hello      #####

      good bye!    ♥♥♥

这里有两个图示,展示了堆栈和附加到函数对象的闭包。

当函数从maker返回时

稍后调用函数时

当通过参数或非本地变量调用函数时,代码需要局部变量绑定,例如margin_top、padding以及a、b、n。为了确保函数代码正常工作,maker函数的堆栈帧应该是可访问的,而这个堆栈帧已经很久以前消失了,但我们可以在函数消息对象中找到备份的闭包。


我想删除这个答案。我意识到这个问题不是关于什么是闭包,所以我想将它移动到另一个问题中。 - Eunjung Lee
2
我相信你有能力删除自己的内容。在答案下面点击“删除”链接。 - Rory McCrossan

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