[JS奇怪的世界]No.38 閉包(一)

前言

這章節將會開始講解一個非常恐怖又惡名昭彰的東西「閉包」(closuere),由於我們要深入瞭解這個程式語言,所以閉包就是一個非常重要的觀念,非常多人對於閉包非常感到混淆甚至討厭,因為這真的很難懂。

閉包

由於一開始我們必須先撰寫一些程式碼來展示閉包的威力,然後再來瞭解它背後的機制及好用之處 ↓

1
2
3
4
5
function greet(whattosay) {
return function(name) {
console.log(whattosay + ' ' + name);
}
}

這時候你可能會說,我該如何呼叫放在 return 中的函數,其實只要這樣就可以了 ↓

1
2
3
4
5
6
7
function greet(whattosay) {
return function(name) {
console.log(whattosay + ' ' + name);
}
}

greet('Hi')('Tomy');

return function

雖然內部函數沒有 whattosay 變數,但因為範圍鏈所以會參考外部環境,進而找到 whattosay,這邊目前看起來還算合理。

但是接下來這個範例就比較不太正常了 ↓

1
2
3
4
5
6
7
function greet(whattosay) {
return function(name) {
console.log(whattosay + ' ' + name);
}
}
var sayHi = greet('Hi');
sayHi('Tony');

閉包

為什麼 sayHi 還是知道 whattosay 是什麼?你一定會說我為什麼這樣講,讓我們試著直接輸出 sayHi 你就會知道為什麼了 ↓

1
2
3
4
5
6
7
8
function greet(whattosay) {
return function(name) {
console.log(whattosay + ' ' + name);
}
}
var sayHi = greet('Hi');
console.log(sayHi);
sayHi('Tony');

sayHi

我們可以看到只有內部函數,但完全沒有看到 whattosay 的變數,那它到底是怎麼找到的?

首先當 greet 被執行時執行環境被建立,然後 whattosay 變數被傳入到環境內。

greet

接下來當 greet 執行完畢後回傳了一個物件函數,所以 greet 就離開了執行堆並留下了 whattosay 的變數。

whattosay

但是每一個執行環境都有屬於它自己的記憶體空間,所以若當執行環境消失了,記憶體空間會發生什麼事情呢?

一般情況下會被清除,而這也稱之為垃圾回收 (Garbage collection)。

但是當執行環境結束時記憶體還是會存在那邊,雖然執行環境消失了,但變數一樣存在某處,然後就回到了全域環境狀況下,並開始執行 sayHi('Tony');

sayHi

那這時候的 sayHi('Tony'); 是一個匿名函數,類似像這樣 ↓

1
2
3
var sayHi = function(name) {
console.log(whattosay + ' ' + name);
}

接下來當我們執行匿名函數後都知道會出現 console.log(whattosay + ' ' + name);,但是我們會注意到裡面並沒有 whattosay 變數,所以 JavaScript 就會往外部環境去找,就是所謂的範圍鏈。

範圍鏈

所以課程上有說明 JavaScript 為了避免我們找不到變數,所以會形成一個框框,將這一整個包住關起來,確保我們可以找的到東西。

框框

而這行為就是所謂的閉包。

閉包

而閉包只是 JavaScript 中的一個特色,確保我們找的到我們想要的東西而已,與我們何時呼叫函數並沒有任何直接關係。

圖源

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

0%