JavaScript教程筆記(8)-閉包函式和IIFE
1 閉包
閉包(closure)是JavaScript語言的一大特色,也是一個難點。理解閉包,首先要理解變數作用域。
作用域有兩種:全域性作用域和函式作用域。函式內部可以訪問全域性變數。
var n = 999;
function f() {
console.log(n);
}
f(); // 999
上面程式碼中,函式 f 可以讀取全域性變數 n。
但是,函式外部不能訪問函式內部的變數。
function f() {
var n = 999;
}
console.log(n); // Error
上面程式碼中,函式內部宣告的變數 n 在外部無法訪問。
如果一定要得到函式內的區域性變數,只有通過變通方法才能實現,那就是在函式內部再定義一個函式。
function f1() {
var n = 999;
function f2() {
console.log(n);
}
return f2;
}
var result = f1();
result(); // 999
上面程式碼中,函式f2在f1內部,則f1定義的變數對f2都是可見的,而函式f1的返回值就是f2,所以就可以在外部獲得f1的內部變量了。
函式f2就是閉包,即能夠讀取其它函式內部變數的函式,也可以把閉包簡單理解成“定義在函式內部的函式”。
閉包的最大用處有兩個,一是可以訪問函式內部的變數,二是讓這些變數始終儲存在記憶體中,即閉包可以使它的"誕生環境"一直存在。
function createInc(start) {
return function() {
return start++;
};
}
var inc = createInc(5);
inc(); // 5
inc(); // 6
inc(); // 7
上面程式碼中,通過閉包inc使得函式內部變數start始終在記憶體中,每一次呼叫都是在上一次呼叫基礎上進行的。
為什麼會這樣呢?原因在於inc始終在記憶體中,而inc依賴於createInc,因此createInc和內部變數start都始終在記憶體中,不會在呼叫結束後就被回收。
注意,外層函式每次執行都會生成一個新的閉包,而這個閉包又會保留外層函式的內部變數,所以記憶體消耗很大,因此不能濫用閉包。
2 立即呼叫的函式表示式(IIFE)
圓括號()是一種運算子,跟在函式名之後,表示呼叫該函式。比如,print()表示呼叫print函式。
有時需要在定義函式之後,立即呼叫該函式。這時,不能在函式定義之後加上圓括號,否則產生語法錯誤。
function(){/*code*/}(); // Error
上面程式碼錯誤的原因是,JavaScript把出 現在行首的function一律解釋成語句,認為這一段是函式定義,不應該以圓括號結尾,所以就報錯了。
解決方法就是讓function不要出現在行首,讓引擎將其理解成一個表示式。最簡單的處理,就是放在一個圓括號裡面。
(function(){ /* code */ }());
// 或者
(function(){ /* code */ })();
上面兩種寫法都以圓括號開頭,避免了錯誤,這就叫做“立即呼叫的函式表示式”(Immediately-Invoked Function Expression),簡稱IIFE。
注意,上面兩種寫法最後的分號都是必須的。如果沒有分號,JavaScript會將它們連在一起解釋,可能就會報錯。
通常情況下,只對匿名函式使用這種“立即執行的函式表示式”。它的目的有兩個:一是不必為函式命名,避免了汙染全域性變數;二是內部形成了一個單獨的作用域,可以封裝一些外部無法讀取的私有變數。
// 寫法一
var tmp = newData;
processData(tmp);
// 寫法二
(function() {
var tmp = newData;
processData(tmp);
}());
上面程式碼中,寫法二比寫法一更好,因為完全避免了汙染全域性變數。
注:本文適用於ES5規範,原始內容來自 JavaScript 教程,有修改。