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

问题描述 投票:37回答:2

此代码记录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的最新值?

javascript var let
2个回答
42
投票

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

使用let,你有一个块范围,当在for循环中使用时,你会得到每个迭代的新绑定 - 即每个setTimeout回调中的i意味着一个不同的变量,每个变量都有不同的值:第一个是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功能如何被翻译也很有趣。


7
投票

从技术上讲,这就是@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次被记录六次。

© www.soinside.com 2019 - 2024. All rights reserved.