[JS奇怪的世界]No.39 閉包(二)

前言

在網路上搜尋相關閉包知識時,其實都會找到類似的範例程式,但是如果沒有釐清觀念,頓時會覺得很難。

閉包

所以在這一堂課算是驗證自己對於閉包的觀念是否已經清楚了,首先先準備一個函數 ↓

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);

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]()

fs

可以看到答案是 3,絕對不是我下面沒有截圖。

為什麼當它到外部參數找 i 的時候會發現它們都一樣呢?回頭想一下執行堆。

首先在執行堆中 buildFunction() 會開始執行,然後跑三次,然後匿名函數會被建立,可是它們只是建立並沒有被執行,只是將函數放進去陣列中 (arr),接下來執行完畢就回傳 arr,所而 i 在執行完畢後最終停在 3,故而被保存在記憶體中,就像課程簡報一樣。

i

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

i

但是如果我們希望是輸出 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]()

J

這樣就可以達到我們要的效果,而這邊主要使用的是 ES6 的 letlet 的特性就是只會存活在這個區塊,所以當這個區塊結束或離開後,就會被消滅,但在這邊 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

這邊所活用的方式是先期講過的其中一種方式,因為要讓變數獨一無二那就要有一個唯一的執行環境,所以就是立刻執行環境 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]()

IIFE

基本上我覺得課程所講的方式較妥當,因為可以確保有執行環境。

圖源

JavaScript 全攻略:克服 JS 奇怪的部分

0%