想必大家都见过这一道经典的面试题:
for (var i = 1; i <= 5; i++) {
setTimeout(function test() {
console.log(i) // 依次输出:6 6 6 6 6
}, i * 1000);
}
造成这个现象的原因是等到setTimeout
异步执行时,i
已经变成6了。(不了解的小伙伴可以去看下JS事件循环机制(Even Loop))
那么如何使他输出: 1,2,3,4,5呢?
1.闭包, 保存变量
for (var i = 1; i <= 5; i++) {
(function (i) {
setTimeout(function () {
console.log('闭包:', i); // 依次输出:1 2 3 4 5
}, i * 1000);
}(i));
}
在这里创建了一个闭包,每次循环都会把i
的最新值传进去,然后被闭包保存起来。
2.bind
for (var i = 1; i <= 5; i++) {
// 缓存参数
setTimeout(function (i) {
console.log('bind', i) // 依次输出:1 2 3 4 5
}.bind(null, i), i * 1000);
}
实际上这里也用了闭包,我们知道bind会返回一个函数,这个函数也是闭包。
它保存了函数的this指向、初始参数,每次i
的变更都会被bind的闭包存起来,所以输出1-5。
3.let
用let
声明i
也可以输出1-5: 因为let
是块级作用域,所以每次都会创建一个新的变量,所以setTimeout
每次读的值都是不同的。