關於for循環中使用setTimeout的四種解決方案

  咱們先來簡單瞭解一下setTimeout延時器的運行機制。setTimeout會先將回調函數放到等待隊列中,等待區域內其餘主程序執行完畢後,按時間順序先進先出執行回調函數。本質上是做用域的問題。es6

  所以如果這樣將不會獲得想要的結果輸出1.2.3.4.5,而會連續輸出5個6。閉包

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

  這是由於setTimeout是異步執行,每一次for循環的時候,setTimeout都執行一次,可是裏面的函數沒有被執行,而是被放到了任務隊列裏,等待執行。只有主線上的任務執行完,纔會執行任務隊列裏的任務。也就是說它會等到for循環所有運行完畢後,纔會執行fun函數,可是當for循環結束後此時i的值已經變成了6,所以雖然定時器跑了5秒,控制檯上的內容依然是6。異步

  (注意:for循環從開始到結束的過程,須要維持幾微秒或幾毫秒,當定時器跑完一秒以後for循環早已經作完了。)函數

  咱們來看另外一種狀況:spa

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

  由setTimeout的運行機制能夠知道,首先會運行外部的全部主程序,雖然for循環內造成了閉包,可是fun並無發現一個實參因此跟第一個例子並沒有實際差異,仍然是連續輸出5個6。code

解決方案1:閉包

  使用閉包是很經典的一種作法:blog

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

  咱們能夠發現跟預期結果一致,依次輸出1到5,因是由於實際參數跟定時器內部的i有強依賴。隊列

  經過閉包,將i的變量駐留在內存中,當輸出j時,引用的是外部函數的變量值i,i的值是根據循環來的,執行setTimeout時已經肯定了裏面的的輸出了。內存

解決方案2:拆分結構

  咱們還能夠將setTimeout的定義和調用分別放到不一樣部分:作用域

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

  控制檯上輸出依然是依次輸出1到5。

解決方案3:let

  這裏再來講一說使用es6的let來解決此問題:

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

  這個例子與第一個相比,只是把var更改爲了let,但是控制檯的結果倒是依次輸出1到5。

  由於for循環頭部的let不只將i綁定到for循環中,事實上它將其從新綁定到循環體的每一次迭代中,確保上一次迭代結束的值從新被賦值。setTimeout裏面的function()屬於一個新的域,經過var定義的變量是沒法傳入到這個函數執行域中的,經過使用let來聲明塊變量能做用於這個塊,因此function就能使用i這個變量了;這個匿名函數的參數做用域和for參數的做用域不同,是利用了這一點來完成的。這個匿名函數的做用域有點相似類的屬性,是能夠被內層方法使用的。

解決方案4:setTimeout第三個參數

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

  因爲每次傳入的參數是從for循環裏面取到的值,因此會依次輸出1到5。關於setTimeout第三個參數,下一篇會詳細講到,這裏你們瞭解下就好。

相關文章
相關標籤/搜索