前言
在網路上搜尋相關閉包知識時,其實都會找到類似的範例程式,但是如果沒有釐清觀念,頓時會覺得很難。
閉包
所以在這一堂課算是驗證自己對於閉包的觀念是否已經清楚了,首先先準備一個函數 ↓
1 2 3 4 5 6 7 8 9 10 11
| function buildFunction () { var arr = []; for(var i = 0; i < 3; i++) { arr.push( function () { console.log(i); } ); } return arr; }
|
接下來將執行 buildFunction()
存入變數 fs
,所以這邊我們可以預期得到一個陣列 ↓
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function buildFunction () { var arr = []; for(var i = 0; i < 3; i++) { arr.push( function () { console.log(i); } ); } return arr; }
var fs = buildFunction (); console.log(fs);
|

所以我們可以這樣呼叫陣列裡面的函數,那預期應該會是什麼?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function buildFunction () { var arr = []; for(var i = 0; i < 3; i++) { arr.push( function () { console.log(i); } ); } return arr; }
var fs = buildFunction(); console.log(fs); fs[0]() fs[1]() fs[2]()
|

可以看到答案是 3,絕對不是我下面沒有截圖。
為什麼當它到外部參數找 i 的時候會發現它們都一樣呢?回頭想一下執行堆。
首先在執行堆中 buildFunction()
會開始執行,然後跑三次,然後匿名函數會被建立,可是它們只是建立並沒有被執行,只是將函數放進去陣列中 (arr),接下來執行完畢就回傳 arr
,所而 i
在執行完畢後最終停在 3,故而被保存在記憶體中,就像課程簡報一樣。

它們的環境是處於閉包,在匿名函數裡面它們找不到 i
,所以會從範圍鏈去取得 i
,而 i
目前為 3,這也是為什麼 i
會是 3 的原因。

但是如果我們希望是輸出 0…1…2 呢?那就這樣做。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function buildFunction () { var arr = []; for(var i = 0; i < 3; i++) { let j = i; arr.push( function () { console.log(j); } ); } return arr; }
var fs = buildFunction(); console.log(fs); fs[0]() fs[1]() fs[2]()
|

這樣就可以達到我們要的效果,而這邊主要使用的是 ES6 的 let
,let
的特性就是只會存活在這個區塊,所以當這個區塊結束或離開後,就會被消滅,但在這邊 j
會被儲存在不同的記憶體位置中,所以每次 for 迴圈執行一次,就會產生一個新的記憶體位置的 j
。
那另一種作法呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function buildFunction () { var arr = []; for(var i = 0; i < 3; i++) { let j = i; arr.push( (function () { console.log(j); }) ); } return arr; }
var fs = buildFunction(); console.log(fs); fs[0]() fs[1]() fs[2]()
|

這邊所活用的方式是先期講過的其中一種方式,因為要讓變數獨一無二那就要有一個唯一的執行環境,所以就是立刻執行環境 IIFE。
但是課程的寫法是這樣。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function buildFunction () { var arr = []; for(var i = 0; i < 3; i++) { let j = i; arr.push( (function (j) { return function () { console.log(j); } })(i) ); } return arr; }
var fs = buildFunction(); console.log(fs); fs[0]() fs[1]() fs[2]()
|

基本上我覺得課程所講的方式較妥當,因為可以確保有執行環境。
圖源
JavaScript 全攻略:克服 JS 奇怪的部分