for循環與setTimeout

// 修改下面代碼,使其可以正確運行
for(var i = 0; i < 6; i++) {
    setTimeout(function timer(){
        console.log(i)
    }, i * 1000)
}

爲何上面代碼的執行結果是 [6,6,6,6,6,6] ?
首先說明,這與異步和做用域都有關係。
先來講和異步的關係,setTimeout是異步操做,所謂的異步就是會在同步操做所有執行完成以後,纔開始執行的。放在上面的代碼中解釋就是,setTimeout是在每次for循環的時候都會調用的(用於將setTimeout內部的代碼放入隊列,在同步代碼執行完成以後,再根據定時執行),可是setTimeout中的代碼是在for循環結束以後纔開始執行的,因此當for循環執行完成的時候,i變成了6,那麼timer中對i的引用也變成了6。babel

而後是和做用域的關係,for循環中定義的變量i ,它的做用域是什麼?異步

for(var i = 0; i<6; i++)   ---->  var i = 0; for(i; i<6; i++)

是 window,因此當for循環執行完成的時候,全局變量i的值爲6,此時去執行隊列中的timer函數,可是timer中並無定義i,因此就會沿着做用域鏈向外層查找,就找到了window中的全局變量i,值爲6。函數


// 第一種方法:使用當即執行函
for(var i = 0; i < 6; i++) {
    (function(j){
        setTimeout(function timer(){
            console.log(j)
        }, j * 1000)
    })(i)
}

// 第二種方法:使用let關鍵字
for(let i = 0; i < 6; i++) {
    setTimeout(function timer(){
        console.log(i)
    }, i * 1000)
}

這兩種方法的本質實際上是同樣的,都是建立了一個新的變量去保存for循環中每次變化的i的值,再將其傳遞給timer,使timer每次在執行的時候都能獲得正確的值。在使用let的時候,能夠看到babel將其轉譯以後的結果,使用了一個新的參數來保存每次循環時的i,因此在6次循環中,會開闢出6個內存空間,保存着6個不一樣的i ,這樣的話,setTimeout中對i的引用就能獲得正確的值。spa

clipboard.png

相關文章
相關標籤/搜索