为什么 let 和 var 绑定在使用 setTimeout 函数时表现不同?
Why let and var bindings behave differently using setTimeout function?
此代码记录 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
的最新值?
使用 var
你有一个函数作用域,并且所有循环迭代只有一个共享绑定 - 即每个 setTimeout 回调中的 i
表示相同的变量最后在循环迭代结束后等于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 在评论中将 link 发布到 Babel.js。请参阅评论中的 link 以获取实时演示,您可以在其中更改代码中的任何内容并立即观看翻译。看看其他 ES6 特性是如何被翻译的也很有趣。
从技术上讲,@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
时,编译器会在最近的功能块timer
中找到变量i
] 因为我们已经到达了 for
循环的末尾,所以 i
保持值 6,并执行 clog
。这就解释了 6 被记录了六次。
此代码记录 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
的最新值?
使用 var
你有一个函数作用域,并且所有循环迭代只有一个共享绑定 - 即每个 setTimeout 回调中的 i
表示相同的变量最后在循环迭代结束后等于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 在评论中将 link 发布到 Babel.js。请参阅评论中的 link 以获取实时演示,您可以在其中更改代码中的任何内容并立即观看翻译。看看其他 ES6 特性是如何被翻译的也很有趣。
从技术上讲,@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
时,编译器会在最近的功能块timer
中找到变量i
] 因为我们已经到达了 for
循环的末尾,所以 i
保持值 6,并执行 clog
。这就解释了 6 被记录了六次。