【问题标题】:JavaScript closures behave unexpectedlyJavaScript 闭包行为异常
【发布时间】:2019-10-11 10:40:09
【问题描述】:

当我执行此代码时,我预计会在控制台中看到 100 次写入 10 次(基于使用其他语言的经验):

const arr = [];
for (let i = 0; i < 10; i++)
    arr.push(() => i * i);

arr.forEach(f => console.log(f()));

但我得到每个数字的平方(从 0 到 9)。这似乎与其他一些语言的行为不同。

例如使用 C#,你将得到 100 十次:

    var list = new List<Func<int>>();

    for(var i = 0; i < 10; i++)
        list.Add(() => i * i);

    list.ForEach(f => Console.WriteLine(f()));

Python 打印 81 十次:

    arr = []
    for i in range(10):
        arr.append(lambda: i * i)

    for f in arr:
        print(f())

Java 不允许这样做,因为捕获的变量不能变异。

有关其他语言的示例,请参阅 this

正如 Mark 在下面解释的,当我们在 JavaScript 中使用 let(块作用域)时,i 的值被捕获到每个匿名函数内的一个新作用域变量中。当您查看每个函数的实际定义时(使用: arr.forEach(f => console.dir(f)); ),您会看到有一个范围(_loop)来自每次迭代都会捕获 i 的值:

  [[Scopes]]: Scopes[3]
  0: Closure (_loop) {i: 0}
  [[Scopes]]: Scopes[3]
  0: Closure (_loop) {i: 1}
   and so on...

如果我们使用 var 重新运行此示例,不出所料,_loop 块级范围会丢失,而 i 会保存在模块/文件级范围中,而不是使用相同的值 (10) for每次迭代:

  [[Scopes]]: Scopes[2]
  0: Closure (./src/index.js) {i: 10}

【问题讨论】:

  • 'i' 不是参考,你怎么期望 100
  • @DennisVash 我知道这不是参考,但我的意思是它的行为与其他一些语言不同,例如尝试在 C# 中运行相同的代码。
  • @DennisVash 这不完全是关于原始值与参考值的问题(请参阅下面 Mark 的回复),因此请考虑删除或编辑您的 cmets 以确保其他人不会被误导。
  • 您在 JS 中使用 let,在 C++ 中使用 var,然后想知道为什么它们的结果不同?
  • @NiettheDarkAbsol 我没有提供 C++ 中的示例,如果您指的是 C# 示例,它使用与 JavaScript var 非常不同的隐式类型局部变量。

标签: javascript closures


【解决方案1】:

这就是使用 let 在 javascript 中声明变量的本质——它的作用域是(在此隐含的)for 循环块,这意味着每次迭代都会获得一个新的作用域名称,该名称会在每个闭包中捕获。闭包并不都使用相同的名称,就像在其他一些语言中或在 JS 中使用 var 声明变量时一样。

要获得预期的行为,请使用var

const arr = [];
for (var i = 0; i < 10; i++)
    arr.push(() => i * i);

arr.forEach(f => console.log(f()));

上面的代码是许多困惑的程序员的祸根,这也是可以选择使用let 的原因之一。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-04-16
    • 2017-04-24
    • 2014-07-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-29
    • 2010-10-02
    • 1970-01-01
    相关资源
    最近更新 更多