# 作用域相关的问题

# 作用域解析代码片段

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