使用setTimeout函数,为什么让和var绑定的行为不同?

这个问题已经在这里有了答案:

  • 使用“ let”和“ var”有什么区别? 33个答案
  • ``let''的说明和for循环的块作用域 3个答案

该代码记录6,共6次:

(function timer() {
  for (var i=0; i<=5; i++) {
    setTimeout(function clog() {console.log(i)}, i*1000);
  }
})();

但是这段代码...

(function timer() {
  for (let i=0; i<=5; i++) {
    setTimeout(function clog() {console.log(i)}, i*1000);
  }
})();

...记录以下结果:

0
1
2
3
4
5

为什么?

是因为let将每个项目绑定到内部范围的方式不同,而var保留了i的最新值吗?

user2290820 asked 2020-07-21T20:21:26Z
2个解决方案
43 votes

使用let,您可以拥有一个函数作用域,并且所有循环迭代都只有一个共享绑定-即每个setTimeout回调中的let都意味着在循环迭代结束后最终等于6的相同变量。

使用let时,您具有块作用域,并且在let循环中使用时,将为每次迭代获取新的绑定-即每个setTimeout回调中的let意味着不同的变量,每个变量都有不同的值:第一个为0, 下一个是1等

所以这:

(function timer() {
  for (let i = 0; i <= 5; i++) {
    setTimeout(function clog() { console.log(i); }, i * 1000);
  }
})();

仅使用var等效于此:

(function timer() {
  for (var j = 0; j <= 5; j++) {
    (function () {
      var i = j;
      setTimeout(function clog() { console.log(i); }, i * 1000);
    }());
  }
})();

使用立即调用的函数表达式以类似于块作用域在示例中使用let的方式使用函数作用域。

不用使用let名称就可以写得更短一些,但是也许不清楚:

(function timer() {
  for (var i = 0; i <= 5; i++) {
    (function (i) {
      setTimeout(function clog() { console.log(i); }, i * 1000);
    }(i));
  }
})();

甚至更短的箭头功能:

(() => {
  for (var i = 0; i <= 5; i++) {
    (i => setTimeout(() => console.log(i), i * 1000))(i);
  }
})();

(但是,如果您可以使用箭头功能,则没有理由使用let。)

这是Babel.js将let的示例转换为在没有let的环境中运行的方式:

"use strict";

(function timer() {
  var _loop = function (i) {
    setTimeout(function clog() {
      console.log(i);
    }, i * 1000);
  };

  for (var i = 0; i <= 5; i++) {
    _loop(i);
  }
})();

感谢Michael Geary在评论中发布了Babel.js的链接。 请参阅评论中的链接以获取实时演示,您可以在其中更改代码中的任何内容并立即观看翻译。 有趣的是,还可以看到其他ES6功能也如何翻译。

rsp answered 2020-07-21T20:22:31Z
7 votes

从技术上讲,这就是@rsp在其出色答案中的解释方式。 这就是我喜欢了解事物在幕后运作的方式。 对于使用i的第一段代码

(function timer() {
  for (var i=0; i<=5; i++) {
    setTimeout(function clog() {console.log(i)}, i*1000);
  }
})();

您可以想象编译器在for循环中会像这样

 setTimeout(function clog() {console.log(i)}, i*1000); // first iteration, remember to call clog with value i after 1 sec
 setTimeout(function clog() {console.log(i)}, i*1000); // second iteration, remember to call clog with value i after 2 sec
setTimeout(function clog() {console.log(i)}, i*1000); // third iteration, remember to call clog with value i after 3 sec

等等

由于使用var声明了i,因此当调用clog时,编译器会在最接近的函数块timer中找到变量i,并且由于我们已经到达for循环的末尾,因此i保留值6,并执行clog。 说明6被记录了六次。

Quannt answered 2020-07-21T20:23:05Z
translate from https://stackoverflow.com:/questions/31285911/why-let-and-var-bindings-behave-differently-using-settimeout-function