[JavaScriptWeird]No.65 作用域陷阱

前言

到了我最喜歡的部分囉~我滿喜歡透過小範例來討論一些寫程式可能會遇到的一些問題,這非常實用,在這個小節內我們要使用一些不一樣的做法來解決這個問題哦。

題目君 1 號

1
2
3
4
5
6
7
var arr = [];  
for (var i =0; i<5; i++){
arr[i] = function(){
console.log(i);
}
}
arr[0](); // 5

這是蠻容易遇到的問題,在此的輸出不會如我們所想會是 0 ,而是變成 5 ,而我們接下來要嘗試不同的方法把問題修正。

產生非預期的原因為何

  • 因為並沒有使用函式包覆,因此再迴圈內宣告的 i 變數相當於全域變數
  • 而執行迴圈時,並沒有執行函式,僅將函式放入陣列
  • 而呼叫後的函式由於在所屬作用域找不到 i ,轉而向上層尋找 i

解法 A

這邊提到第一種解法,可以多宣告一個函式並且使用閉包技巧來記住當前 i 的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
var arr = [];  
for (var i =0; i<5; i++){
arr[i] = logN(i);
}

function logN(num){
return function(){
console.log(num);
}
}

arr[0](); // 0
arr[1](); // 1

可以把它想像成這樣

解法 B

如果不想要額外宣告函式,也可以透過 IIFE 配合閉包的技巧來達成,關於 IIFE 的部分可以喚醒另外一個世界線的記憶(?

所以這段程式其實可以改寫成這樣,跟解法 A 差不多,只是把額外宣告函式的部分用 IIFE 取代掉了。

1
2
3
4
5
6
7
8
9
10
11
var arr = [];  
for (var i =0; i<5; i++){
arr[i] = (function(num){
return function(){
console.log(num);
}
})(i)
}

arr[0](); // 0
arr[1](); // 1

解法 C

我個人比較喜歡的一種,因為最容易。那就是使用 ES6 新增的 let 來處理這個問題, let 的作用域是以 block 也就是大括號來劃分,而 let 在迴圈中的表現出的特性又有點不同,每次迴圈執行時都會產生一個不同的 i 。

1
2
3
4
5
6
7
8
var arr = [];  
for (let i =0; i<5; i++){
arr[i] = function(){
console.log(i);
}
}

arr[0](); // 0

使用 let 來處理這樣的問題其實相當容易,就只是把 var 替換掉而已。

然而我們可以把每當迴圈執行時的這段過程想像成這樣

而實際把右邊的內容放到左邊執行,結果是一樣的。

0%