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

时间:2015-07-08 07:17:21

标签: javascript var let

此代码记录css(),共6次:

input.login-error {
    border: 3px solid red;
}
p.login-error {
    color: red;
}

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

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的最新值?

2 个答案:

答案 0 :(得分:37)

使用 var ,您有一个函数范围,并且只有一个共享绑定用于所有循环迭代 - 即每个setTimeout回调中的i表示循环迭代结束后 finally 等于6的相同变量。

使用 let ,您有一个块范围,当在for循环中使用时,您会为每次迭代获得一个新的绑定 - 即每个迭代中的i setTimeout回调意味着一个不同的变量,每个变量都有不同的值:第一个为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的示例中的块作用域类似。

如果不使用j名称,可以写得更短,但也许不会那么清楚:

(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);
  }
})();

(但如果您可以使用箭头功能,则没有理由使用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);
  }
})();

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

答案 1 :(得分:5)

从技术上来说,这就是@rsp在他出色的答案中所解释的。这就是我喜欢理解引擎下工作的方式。对于使用var

的第一个代码块
(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

等等

因为i是使用var声明的,所以当调用clog时,编译器会在最近的功能块i中找到变量timer,由于我们已经到达for循环的末尾,i保持值6,并执行clog。这解释了6次被记录六次。