App2 - for + setTimeout

開啟 Console 看輸出。

var 與 let 的差異詳細說明:

1. 使用 var 的情況:

原因: var 具有函數作用域(function scope)而非區塊作用域(block scope)。在 for 循環中,只有一個變數 i 被創建,所有 setTimeout 回調函數共享這個變數。

執行流程:

  1. for 循環開始執行:i = 0, 1, 2, 3, 4
  2. 每次循環都創建一個 setTimeout,但這些 setTimeout 並不會立即執行,而是被排入事件隊列等待 1 秒
  3. 在這 1 秒內,for 循環繼續執行直到完成,此時 i 的最終值變為 5
  4. 1 秒後,所有 setTimeout 的回調函數同時執行,但此時它們引用的是同一個變數 i(其值已為 5)
  5. 結果:全部 five 個 setTimeout 都打印出 5

結果: 輸出 "5 5 5 5 5"

問題總結: 當 setTimeout 還在計時時,for 循環早已完成(在 1 秒內就完成了),var 宣告的變數 i 的值已經是 5 了。

2. 使用 let 的情況:

原因: let 具有區塊作用域(block scope)。在 for 循環中,每次迭代都會創建一個新的變數實例。

執行流程:

  1. 第一次循環:let i = 0,創建第一個 setTimeout(捕獲 i = 0)
  2. 第二次循環:let i = 1,創建第二個 setTimeout(捕獲 i = 1)
  3. 第三次循環:let i = 2,創建第三個 setTimeout(捕獲 i = 2)
  4. 第四次循環:let i = 3,創建第四個 setTimeout(捕獲 i = 3)
  5. 第五次循環:let i = 4,創建第五個 setTimeout(捕獲 i = 4)
  6. 循環結束,但每個 setTimeout 都綁定了它創建時的 i 值
  7. 1 秒後,所有 setTimeout 按順序執行,各自打印出它們捕獲的 i 值

結果: 輸出 "0 1 2 3 4"

問題總結: 並不是「重設」變數,而是每次循環都創建了一個新的 let 變數副本。每個 setTimeout 捕獲的是當下循環中那個特定的變數副本,所以當計時結束時,它們各自保留著當時的值。

3. 技術術語:

這種現象也稱為 "閉包陷阱""循環變數捕獲問題"。ES6 的 let 通過為每次循環迭代創建新的詞法環境解決了這個問題。