1. 程式人生 > >JavaScript教程筆記(8)-閉包函式和IIFE

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 教程,有修改。