iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 30
0
Modern Web

JavaScript Note系列 第 30

window.setTimeout

之前有看過一個題目,個人認為這題目非常有助於釐清觀念,所以特地另開章節來討論。

請設計一個程式,每隔一秒鐘,依序輸出12345。

恩,很簡單啊,不就是這樣囉:

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

真的是這樣寫的嗎?請各位自己看看輸出結果吧。

想必觀念未釐清的新手,一定滿頭問號,為什會是這結果?一秒鐘後,一次出現5個6。

先解釋第一個問題,為何一秒鐘就全部輸出?

我們先拆成兩個部分來看,for迴圈跟setTimeout( )。

如果改成這樣呢?

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

https://ithelp.ithome.com.tw/upload/images/20181114/20112573IGxBu1yk7t.png
請想一下這段程式在哪完成的?是不是call stack,所以不會丟到queue等待處理是吧,因此結果瞬間出現。

setTimeout( )是以非同步的方式處理callback function。不管怎樣,一定會設定時間,再丟入queue等待,說穿了,setTimeout( )存活時間極短,它的任務只是把callback function丟到他處,繼續計時。

所以for是這樣處理setTimeout( )的:
它一口氣跑完5個setTimeout( ),至於定時多少,或是callback function內容是啥,不甘它的事,它的任務只是把迴圈跑完而已。

以下是另一種表示法:

setTimeout(() => {
    console.log(i);
}, 1000);
setTimeout(() => {
    console.log(i);
}, 1000);
setTimeout(() => {
    console.log(i);
}, 1000);
setTimeout(() => {
    console.log(i);
}, 1000);
setTimeout(() => {
    console.log(i);
}, 1000);

幾乎在同一時間觸發5個setTimeout( ),所以5個值才會在時間(1秒)一到,幾乎同時出現。

接下來,為何都是6呢?

之前講過,JavaScript區域跟全域變數是以函式來區分的(以var宣告的話),函式內是區域,外是全域。

所以callback function的i是全域變數,至於callback function怎麼處理i,不甘for的事,for在一瞬間就跑完迴圈,觸發5個setTimeout( ),也表示i已經累加到6了。

那這5個callback function從queue拉到call stack運算時,此時遇到的i是多少?
是6,所以才會出現5個6。

既然知道問題了,我們要怎麼解決?

先從i著手,我們要想辦法,在每跑完一次迴圈,留住i的狀態,意味著不要讓它受到全域的影響,想到了嗎?

沒錯,使用IIFE可以做到。

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

https://ithelp.ithome.com.tw/upload/images/20181114/20112573wOdvcC9oR3.png
這時,for執行IIFE,再由IIFE觸發setTimeout( )的時候,把當時i的狀態,一併丟給callback function,所以等到callback function執行時,它所得到的i,不是全域i,而是IIFE丟給它的區域i。

那時間問題呢?

很簡單,既然能取得i的狀態,直接加入計時不就好了。

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

解決了,不過有個更簡單的辦法,使用let宣告,將變數限制在for迴圈內。

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

參考來源:
重新認識 JavaScript: Day 18 Callback Function 與 IIFE


上一篇
Promise catch
下一篇
Async & Await
系列文
JavaScript Note31

尚未有邦友留言

立即登入留言