MDN怎麼說?
閉包讓你可以在一個內層函數中訪問到其外層函數的作用域。在 JavaScript 中,每當創建一個函數,閉包就會在函數創建的同時被創建出來。
Scope
在了解閉包前,我們要先知道 Javascript 採用的是語彙範疇 ( Lexical Scope ) ,語彙範疇的特性是由所在的 Scope 往外層的 Scope 查找,一但找到就停止,或直到 Global 為止,且外層無法查找到內層。這個行為我們叫做「範圍鏈」(Scope Chain)。
function outer() {
let name = "Chloe"
function inner() {
console.log ('Hi,',name);
}
inner()
}
outer(); // Hi, Chloe
outer() 中的函式 inner(),明明就沒有定義name的變數,卻可以打印出來,就是因為 name 去往外層的Scope去查找,在outer的 Scope 中找到了 name 的變數。如果你沒有學過其他語言,可能覺得是理所當然(我就是,一開始不懂為什麼要一直被拿出來強調...),後來才知道,在其他語言中,函式內的局部變數,只會在函式的執行期間存在。
總之要記住的就是:Javascript 會由內層往外層查找變數,找到就停止。反之則不行。
閉包
變數私有
為什麼要先了解 Scope 呢?我們換個方式再看一次剛剛的程式碼。
function outer(name) {
return function inner() {
console.log ('Hi,',name);
}
}
let innerFunc = outer('Chloe'); // Hi, Chloe
innerFunc()
- 建立一個函式 outer(),對其傳入參數 name,此參數只作用在 outer() 內,沒有任何參考指向他時,就會釋放記憶體。
- innerFunc() 雖然跳過 outer() 直接執行 inner() ,此時 name 需要一個參考值,所以往外層尋找,在 outer 找到了參數 name ,並在 innerFunc 時,參數的值只向 "Chloe" 。
- name 的變數持續被參考,因此會留在記憶體,此時 name 就變成 innerFunc 的私有變數,其他人無法修改也無法取得。
閉包是什麼?閉包就是⋯
當 inner 值回傳後,除了自己本身的程式碼,也能取得在 outer 的變數值,記住了在 outer 的執行環境,延長了 outer 中變數的作用時間,這就是閉包。而 innerFun 存取了 name的變數,讓變數私有。
閉包特性 - 封裝
再舉個應用方式 ↓
function add() {
let start = 0
return function(x) {
return start += x
}
}
let addFunc = add();
addFunc(3) // 3
addFunc(3) // 6
addFunc(3) // 9
let addFunc2 = add();
addFunc2(3) // 3
addFunc2(4) // 7
addFunc2(5) // 12
可以發現 addFunc 與 addFunc2 分別是兩個「獨立」的實體,這個概念是不是似曾相似呢?沒錯,閉包具有物件導向特性之一「封裝」的概念在。