关于javascript:setTimeout中let和var的区别?

Difference between let and var inside setTimeout?

我知道let和var的区别,let是块范围,var是功能范围。

1
2
3
4
5
6
7
8
9
for(var i=0; i< 3; i++){
    setTimeout(function(){
        console.log(i);
    }, 10);
}

output : 3
         3
         3

我知道上面的代码片段是如何工作的(当i的值为3时,console.log(i)正在执行,因为i的范围是全局的)。

但是

1
2
3
4
5
6
7
8
9
for(let i=0; i< 3; i++){
    setTimeout(function(){
        console.log(i);
    }, 10);
}

output : 1
         2
         3

上面的代码片段让我困惑。据我所知,它应该抛出引用错误(因为执行console.log(i)时,将在全局范围内查找i的值,而不是在本地范围内,并且我没有在全局中声明/定义。所以应该给出参考误差。)

谁能解释第二个for循环如何在运行时工作?


这就是结束的魔力。在你的循环中

1
2
3
4
5
for(let i=0; i< 3; i++){
    setTimeout(function(){
        console.log(i);
    }, 10);
}

您正在声明一个函数

1
2
3
function(){
  console.log(i);
}

此外,循环本身声明了一个块

1
2
3
4
for(let i=0; i< 3; i++){
  // this is a block scope because it is contained in
  // braces
}

let定义的变量是块范围的。

由于闭包,您在循环中声明的函数可以访问其作用域和其父作用域中声明的所有变量,直到它被垃圾收集为止。

A closure is the combination of a function and the lexical environment
within which that function was declared. This environment consists of
any local variables that were in-scope at the time that the closure
was created.

创建setTimeout使用的函数时,变量i在作用域内。对于循环的每个迭代,引用的ii的不同实例。

函数存在,直到您声明的间隔通过为止。这就是为什么在循环中声明的3个函数中的每一个都会打印i的值;它在包含范围中声明,并且仍然对函数可用。


第二个例子(使用let)起作用,因为函数在声明时将关闭作用域中的所有变量。for循环的每次迭代都会使用let创建一个新变量,超时中的函数会关闭该变量并保持引用。当函数在超时后被取消引用时,它的闭包变量也是如此。

有关函数关闭的更多信息,请参阅JavaScript闭包如何工作。


在此上下文中使用let时,将在每次迭代中创建一个新的绑定/作用域。如果您想在ES5中使用var实现类似的行为,则必须使用iife:

1
2
3
4
5
6
7
for (var i = 0; i < 3; i++) {
  (function (i) {
    setTimeout(function () {
      console.log(i);
    }, 10);
  })(i);
}