1. 程式人生 > >for 迴圈中的setTimeout(function(){})非同步問題

for 迴圈中的setTimeout(function(){})非同步問題

閱讀這段程式碼

    for (let i = 1; i <= 5; i++) {
        setTimeout(function timer() {
            console.log(i);
        }, i*1000)
    }

預期是分別輸出數字1-5, 每秒1次,每次1個
結果卻是每秒一次輸出了5個6

1. setTimeout的執行原理

setTimeout()是一個非同步方法, 傳遞一個函式,
延遲一段時候把該函式新增到隊列當中,並不是立即執行,
而且必須等當前環境所有程式碼執行完以後, 才會執行

也就是說我們執行這個for迴圈的時候

    setTimeout(fun(...), 1000)
    setTimeout(fun(...), 2000)
    setTimeout(fun(...), 3000)
    setTimeout(fun(...), 4000)
    setTimeout(fun(...), 5000)

五個函式先進入了佇列, 然後等for迴圈結束後再依次出隊 (粗略理解, 實質上是回撥函式)
for迴圈結束後, 此時i是等於6的, 所以每秒一次輸出了5個6

要注意到var定義的i實質上是全域性變數, 等同於下面的程式碼

    var i
    for (i = 1; i <= 5; i++
) { ... }

2. 解決辦法

其實還是詞法作用域的問題,我們從作用域下手,解決這個問題

2.1 使用閉包

這個辦法的原理是建立了閉包作用域, 每次迴圈會生成一個新的閉包作用域, 使得延遲函式回撥可以訪問到正確的值

注意, 閉包作用域裡必須宣告變數j, 如果是一個空的作用域, 那不會產生作用

    for (var i = 1; i <= 5; i++) {
        (function(j) {
            setTimeout(function timer() {
                console.
log(j); }, j*1000) })(i) }

2.2 使用ES6中的let

這個辦法的原理是通過let來劫持塊作用域, 注意, 這個變數i不只會宣告一次, 每次迭代的時候都會宣告i, 每次迭代後, i的值都會使用

上一個迭代的值來初始化這個變數, 形成一個塊作用域

    for (let i = 1; i <= 5; i++) {
        setTimeout(function timer() {
            console.log(i);
        }, i*1000)
    }