淺談 Closure 閉包
變數的作用域
要理解閉包必須先理解 JS 的變數作用域,在 JS 裡變數的作用域分為全域變數與函數內部的變數。
JS 特別的地方在於函數內部可以讀取全域變數:
var num = 99;
function func() {
console.log(num); // 99
}
func();
反之,在函數外部則無法讀取函數內部的變數:
function func() {
var num = 99;
}
console.log(num); // num is not defined
但需要注意一點,函數內部在宣告變數的時候,記得要使用 var
來宣告,如果不用的話,實際上是宣告了一個全域變數:
function func() {
num = 99;
}
func();
console.log(num); // 99
如何從外部讀取函數內部的變數?
那就是在函數的內部,再宣告一個函數:
function func1() {
var num = 99;
function func2() {
console.log(num);
}
return func2;
}
var res = func1();
res(); // 99
上面是一個非常簡單及經典的閉包範例,函數 func2
存在於函數 func1
之中,這時函數 func1
內部的變數對於函數 func2
而言都是可見的,但反之,則不可見,函數 func2
內部的變數對於函數 func1
而言就是不可見的,這是 JS 詞彙環境機制下的結果,函數實際上被寫在何處會左右 JS 查找變數時的方向。
所以說既然 func2
可以讀取 func1
內部的變數,那麼我們只要把 func2
回傳,我們就可以在 func1
之外讀取其內部的變數了!
閉包的概念
粗略地來講,閉包就是能夠讀取其它函數內部變數的函數,由於只有函數內部的函數才能讀取函數內部的變數,因此也可以把閉包簡單的理解為定義在一個函數內部的函數。
但我會更偏向於形容閉包就是將函數內部和函數外部鏈接起來的一座橋梁。
閉包的用途
讀取父函數內部的變數
讓父函數的變數始終保存於內存中
關於第二點的範例:
function func1() {
var num = 99;
plusOne = function () {
num += 1;
};
function func2() {
console.log(num);
}
return func2;
}
var res = func1();
res(); // 99
plusOne();
res(); // 100
plusOne();
res(); // 101
在上述的範例中,res
實際上就是存儲了一個閉包,res
執行了三次,結果分別為 99,100,101 這證明了函數 func1
內部的變數 num
始終保存於內存中沒有釋放,這是就是一個閉包最明顯的特徵。
為什麼會這樣呢?
原因在於 func1
是 func2
的父函數,而 func2
被賦值給了一個全域變數 res
,這導致 func2
始終存在於內存中,然而 func2
的存在依賴於 func1
,因此函數 func1
也始終存在於內存中,不會因為在調用結束後就被垃圾回收機制給回收了。