# 作用域相关的问题
# 作用域解析代码片段
for (var i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, i*1000);
}
正常情况下,我们对这段代码的预期结果是每秒一次的输出1,2,3,4,5
然而实际情况并不会跟我们预期想的一样,它会每秒一次的输出五个6
这里我们预期认为每个迭代中一个 timer
输出对应的 i
的值。可实际情况是尽管循环中的五个函数是在各个迭代中分别定义的, 但是它们都被封闭在一个共享的全局作用域
中,因此实际上只有一个 i
。每次执行的都是 i
的最终值。
在JavaScript中,由于有EVENT Loop的机制存在, setTimeout
会被放置在主线程任务完成之后再执行,所以在 timer
每次生成的时候传入的 i
其实都是 for
循环之后的最终值。这就是会输出五个6的原因。
这段代码其实和上面那段代码的反应是一致的。
for (var i=1; i<=5; i++) {
setTimeout( timer, i*1000);
}
function timer() {
console.log( i );
}
如果需要实现我们想要的效果,可以使用以下做法。
# 1.把值传入回调函数,让timer的函数作用域自己备份i的值
for (var i=1; i<=5; i++) {
setTimeout( function timer(i) {
console.log( i );
}, i*1000, i); // 把i当作参数传入timer
}
# 2.在调用的地方形成一个作用域,把i放进作用域里面保存副本
for (var i=1; i<=5; i++) {
function t(i){
setTimeout( function timer() {
console.log( i );
}, i*1000);
}
t(i);
}
这段代码可以用IIFE(立即执行函数)重写下
for (var i=1; i<=5; i++) {
(function(i){
setTimeout( function timer() {
console.log( i );
}, i*1000);
})(i);
}
# 3.通过 let
定义 i
let
关键字可以将变量绑定到所在的任意作用域中(通常是 {···})内,也就是说 let
可以在代码中实现一个块级作用域。
for (let i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, i*1000);
}