为什么让set和var绑定使用setTimeout函数表现不同?

use*_*820 45 javascript var let

此代码记录66次:

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

但是这段代码......

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

...记录以下结果:

0
1
2
3
4
5
Run Code Online (Sandbox Code Playgroud)

为什么?

是因为不同地let绑定到内部范围每个项目并var保持最新值i

rsp*_*rsp 43

有了var你有一个功能范围,只有一个共享您的所有循环迭代的结合-即在i每一个setTimeout的回调意味着相同的变量,最后是循环迭代结束后等于6.

let你有一个块的范围,并在使用时for循环中,您得到每个迭代一个新的结合-即i在每一个setTimeout的回调意味着不同的变量,每个都有不同的价值:第一个是0,下一个就是1等

所以这:

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

相当于仅使用var:

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

使用立即调用的函数表达式来使用函数作用域,其方式类似于块作用域在示例中的作用let.

它可以在不使用j名称的情况下编写得更短,但也许它不会那么清楚:

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

使用箭头功能更短:

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

(但是如果你可以使用箭头功能,那就没有理由使用了var.)

这就是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);
  }
})();
Run Code Online (Sandbox Code Playgroud)

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

  • 只需添加到您的优秀的解释,这里是一个[ES6的代码的翻译ES5由babeljs.io提供(http://babeljs.io/repl/#?experimental=true&evaluate=true&loose=false&spec=false&code=(功能%20timer()%20%7B%0D%0A%20%20for%20(让%20 1%20%3D%200%3B%20I%20%3C%3D%205%3B%20I%2B%2B)% 20%7B%0D 0A%%20%20%20%20setTimeout(函数%20clog()%20%7B%20console.log(ⅰ)%3B%20%7D%2C%20I%20*%201000)%3B %0D 0A%%20%20%7D%0D 0A%%7D)()%3B). (4认同)

Qua*_*nnt 7

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

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

您可以想象编译器在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
Run Code Online (Sandbox Code Playgroud)

等等

由于i使用调用var时进行了声明clog,因此编译器会i在最近的功能块中找到变量,timer并且由于我们已经到达for循环的末尾,因此i将值保存为6,然后执行clog。这说明6被记录了六次。