首页 > 解决方案 > JavaScript 闭包行为异常

问题描述

当我执行此代码时,我预计会在控制台中看到 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不允许这样做,因为捕获的变量不能变异。

有关其他语言的示例,请参见此处

正如 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):

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

标签: javascriptclosures

解决方案


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.


推荐阅读