JavaScript 閉包與作用域問題解題解析:如何正確輸出 1 2 3

更新日期: 2024 年 11 月 4 日

在 JavaScript 中,for 迴圈與 setTimeout 的組合,經常會引發閉包與作用域的問題,導致結果與預期不符。

這篇文章將探討這種情境,並介紹兩種解決方法,使得以下程式碼能夠正確輸出 1 2 3


問題說明(在不能將 var 修改成 let 的條件下)

以下代碼的目標是讓程式輸出 1 2 3

for (var i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i);
  }, 0)
}

console.log(i);

在解決這個問題時,我們必須在不更動 var 宣告的情況下達到正確輸出。

通常,在 JavaScript 中使用 let 來宣告變數可以限制其作用域至每次迴圈內部,但這裡不得更動 var

因此,需要其他方式,使每次迴圈中的 i 值在異步操作時不受後續迴圈影響。

當這段代碼執行時,console.log(i) 的結果並非 1 2 3,而是 3 3 3

原因在於,for 迴圈中的 var i 是一個全域變數,每次迴圈中並未將 i 綁定到該次迴圈。

因此 setTimeout 在迴圈結束後才執行,最終輸出重複的值。


解決思路 1:使用 bind 綁定變數

解決方案

為了解決這個問題,可以使用 bind 方法,將當前的 i 值綁定到一個新的函數中,確保 i 值在每次迴圈中獨立存在,並在異步操作執行時仍然保持該次迴圈的 i 值。

bind 方法的觀念

在 JavaScript 中,bind 方法會創建一個新的函數,並將特定的參數與函數綁定。

此方法可以用來綁定當前 i 值,保證每次迴圈中的 i 值獨立,避免受後續變數影響。

實作代碼

以下是使用 bind 方法綁定變數的程式碼:

for (var i = 0; i < 3; i++) {
  const aa = ((n) => {
    console.log(n);
  }).bind(null, i);

  setTimeout(aa, 0);
}

在這段程式碼中,我們將當前的 i 值作為參數傳遞給 bind 方法,創建了一個新的函數 aa,並綁定當前的 i 值給 n,使其在 setTimeout 執行時能輸出獨立的 i 值。

運行結果

透過 bind,我們為每次迴圈的 i 值創建了一個新的綁定,因此最終輸出將會是 1 2 3,因為每次執行 setTimeout 時,使用的是當前迴圈的 i 值副本。


解決思路 2:使用 IIFE 綁定變數

解決方案

另一種解法是使用立即執行函數 (IIFE),為每次迴圈建立一個新的作用域,使得 i 值在每次執行時獨立存在,確保 i 不會受到後續迴圈的影響。

IIFE 的概念

立即執行函數 (IIFE, Immediately Invoked Function Expression) 是一種 JavaScript 表達式,可以在定義後立即執行。

IIFE 能夠建立新的作用域,在處理異步操作時可以捕捉當前值,避免後續變數的影響。

實作代碼

以下是利用 IIFE 綁定變數的程式碼:

for (var i = 0; i < 3; i++) {
  setTimeout(((n) => {
    console.log(n);
  })(i), 0);
}

在此解法中,我們使用 IIFE 在每次迴圈中立即執行並傳入當前的 i 值。

這樣 n 值在 IIFE 作用域內保持不變,確保每次執行的 setTimeout 引用的是當前 i 的值。

運行結果

此方式通過 IIFE 捕捉每次迴圈的 i 值,並作為局部變數 n 使用,最終結果同樣是 1 2 3,因為 setTimeout 中引用的是當時 n 的獨立值。


結論

透過使用 bind 或 IIFE,能夠在不修改 varlet 的情況下正確捕捉每次迴圈的變數 i 值,解決 JavaScript 中常見的閉包問題。

理解並掌握這些解決方法,將有助於在 JavaScript 中更有效地控制變數作用域。

Similar Posts