在閱讀之前,請先參考 hw5 筆記內容認識 Scope、Call Stack、Web APIs、Event Loop 等名詞,這樣才可以理解以下的步驟。
for(var i=0; i<5; i++) {
console.log('i: ' + i)
setTimeout(() => {
console.log(i)
}, i * 1000)
}
使用 var
來宣告 i 的地方,因為不在 function 裡面,i 變成全域變數,任何地方都可以存取到它。
跑每一次迴圈要執行以下的程式碼,總共要跑五次
console.log('i: ' + i)
setTimeout(() => {
console.log(i)
}, i * 1000)
setTimeout() 不存在於 JavaScript 原始碼內,它屬於瀏覽器提供的 Web APIs 一種,所以瀏覽器會把它移到其他的執行緒執行,因為此時的 i 是 0,瀏覽器設置為 0 毫秒計時(0 * 1000),而 JS 則繼續執行。
因為 i 小於 5,進入第二次迴圈。
第二次迴圈的 setTimeout() 進入到堆疊,同樣因為是 Web APIs 而被 JS 忽略,瀏覽器將它移到其他的執行緒,不影響 JS Runtime 執行。此時的 i 是 1,瀏覽器設置為 1 秒計時(1 * 1000),而 JS 則繼續執行。
因為 i 小於 5,進入第三次迴圈。
瀏覽器將它移到其他的執行緒,設置為 2 秒計時(2 * 1000),而 JS 則接著繼續執行。
因為 i 小於 5,進入第四次迴圈。
瀏覽器將它移到其他的執行緒,設置為 3 秒計時(3 * 1000),而 JS 則繼續執行。
因為 i 小於 5,進入第五次迴圈。
瀏覽器將它移到其他的執行緒,設置為 4 秒計時(4 * 1000),而 JS 則繼續執行。
因為 i 沒有小於 5,結束迴圈,堆疊被清空。
上面那些 console.log('i: ' + i)
其實在程式運行之初就跑完了,剩下的就是等待。
那些被瀏覽器移到其他執行緒的 setTimeout(),是交由瀏覽器提供的執行緒計時,每次計時完裡頭的 callback function () => { console.log(i) }
就會按 0、1、2、3、4 秒被移到 Callback Queue(回調序列),等待 Event Loop 的安排。
Event Loop 一發現堆疊清空了,也就是 i 變成 5 迴圈執行完畢,就會將第一個 () => { console.log(i) }
移到 stack 中執行,呼叫 console.log(i)
,因為 i 原本就是全域變數,之前每一次的賦值其實都是改到同一個變數,所以印出的 i 都是 5。
執行結果為
i: 0
i: 1
i: 2
i: 3
i: 4
5
// 1s
5
// 1s
5
// 1s
5
// 1s
5