javascript中閉包的理解
閉包
閉包(closure)是 JavaScript 語言的一個難點,也是它的特色,很多高階應用都要依靠閉包實現。
理解閉包,首先必須理解變數作用域。前面提到,JavaScript 有兩種作用域:全域性作用域和函式作用域。函式內部可以直接讀取全域性變數。
var n = 999;
function f1() {
console.log(n);
}
f1() // 999
上面程式碼中,函式f1
可以讀取全域性變數n
。
但是,正常情況下,函式外部無法讀取函式內部宣告的變數。
function f1() { var n = 999; } console.log(n) // Uncaught ReferenceError: n is not defined(
上面程式碼中,函式f1
內部宣告的變數n
,函式外是無法讀取的。
如果出於種種原因,需要得到函式內的區域性變數。正常情況下,這是辦不到的,只有通過變通方法才能實現。那就是在函式的內部,再定義一個函式。
function f1() {
var n = 999;
function f2() {
console.log(n); // 999
}
}
上面程式碼中,函式f2
就在函式f1
內部,這時f1
內部的所有區域性變數,對f2
都是可見的。但是反過來就不行,f2
內部的區域性變數,對f1
就是不可見的。這就是 JavaScript 語言特有的"鏈式作用域"結構(chain scope),子物件會一級一級地向上尋找所有父物件的變數。所以,父物件的所有變數,對子物件都是可見的,反之則不成立。
既然f2
可以讀取f1
的區域性變數,那麼只要把f2
作為返回值,我們不就可以在f1
外部讀取它的內部變量了嗎!
function f1() {
var n = 999;
function f2() {
console.log(n);
}
return f2;
}
var result = f1();
result(); // 999
上面程式碼中,函式f1
的返回值就是函式f2
,由於f2
可以讀取f1
的內部變數,所以就可以在外部獲得f1
的內部變量了。
閉包就是函式f2
,即能夠讀取其他函式內部變數的函式。由於在 JavaScript 語言中,只有函式內部的子函式才能讀取內部變數,因此可以把閉包簡單理解成“定義在一個函式內部的函式”。閉包最大的特點,就是它可以“記住”誕生的環境,比如f2
f1
,所以從f2
可以得到f1
的內部變數。在本質上,閉包就是將函式內部和函式外部連線起來的一座橋樑。
閉包的最大用處有兩個,一個是可以讀取函式內部的變數,另一個就是讓這些變數始終保持在記憶體中,即閉包可以使得它誕生環境一直存在。請看下面的例子,閉包使得內部變數記住上一次呼叫時的運算結果。
function createIncrementor(start) {
return function () {
return start++;
};
}
var inc = createIncrementor(5);
inc() // 5
inc() // 6
inc() // 7
上面程式碼中,start
是函式createIncrementor
的內部變數。通過閉包,start
的狀態被保留了,每一次呼叫都是在上一次呼叫的基礎上進行計算。從中可以看到,閉包inc
使得函式createIncrementor
的內部環境,一直存在。所以,閉包可以看作是函式內部作用域的一個介面。
為什麼會這樣呢?原因就在於inc
始終在記憶體中,而inc
的存在依賴於createIncrementor
,因此也始終在記憶體中,不會在呼叫結束後,被垃圾回收機制回收。
閉包的另一個用處,是封裝物件的私有屬性和私有方法。
function Person(name) {
var _age;
function setAge(n) {
_age = n;
}
function getAge() {
return _age;
}
return {
name: name,
getAge: getAge,
setAge: setAge
};
}
var p1 = Person('張三');
p1.setAge(25);
p1.getAge() // 25
上面程式碼中,函式Person
的內部變數_age
,通過閉包getAge
和setAge
,變成了返回物件p1
的私有變數。
注意,外層函式每次執行,都會生成一個新的閉包,而這個閉包又會保留外層函式的內部變數,所以記憶體消耗很大。因此不能濫用閉包,否則會造成網頁的效能問題。
參考連結
- Ben Alman, Immediately-Invoked Function Expression (IIFE)
- Mark Daggett, Functions Explained
- Juriy Zaytsev, Named function expressions demystified
- Marco Rogers polotek, What is the arguments object?
- Juriy Zaytsev, Global eval. What are the options?
- Axel Rauschmayer, Evaluating JavaScript code via eval() and new Function()