立即呼叫函數表達式
立即呼叫函數表達式(英文:immediately-invoked function expression,縮寫:IIFE)[1],是一種利用JavaScript函數生成新作用域的編程方法。
立即呼叫函數表達式可以令其函數中聲明的變數繞過JavaScript的變數置頂聲明規則,還可以避免新的變數被解釋成全域變數或函數名佔用全域變數名的情況。與此同時它能在禁止訪問函數內聲明變數的情況下允許外部對函數的呼叫。有時,這種編程方法也被叫做「自執行(匿名)函數」,但「立即呼叫函數表達式」是語意上最準確的術語。 [2][1][3][4]
用法
立即呼叫函數表達式擁有數種不同的寫法[5]。最常見的一種是將函數表達式字面量置於圓括號(分組運算子)之內,然後使用圓括號呼叫函數。[6][7]
(function() {
// 这里的语句将获得新的作用域
})();
若要將作用域外變數傳遞進函數,則按下述方式書寫:
(function(a, b) {
// a == 'hello'
// b == 'world'
})('hello', 'world');
開頭的括號可能會因為直譯器的分號自動插入特性造成一些問題。括號本用於明確字面量為表達式以與函數宣告區分,但直譯器可能將括號解釋為對以上一行中結尾的變數名為名的函數的呼叫。在一些省略分號的程式中,可見將分號至於行首的做法。這樣的分號被稱為「防禦性分號」[8][9],舉例:
a = b + c
;(function() { // 故意将分号放在这里
// 代码
})();
如此書寫,以防止陳述式被理解為對函數c的呼叫(c(...)
)。
例子
理解立即呼叫函數表達式的關鍵在於認清JavaScript擁有函數作用域,但沒有塊作用域(ES6之前),且通過指標(而非複製)將變數傳入一個函數閉包。[10] ES6 引入了新關鍵字 let和 const,用它們定義的常數和變數具有塊級作用域。
求值上下文(Evaluation context)
缺少塊作用域意味着一個在類似於for迴圈的塊中聲明的變數會被置頂到其所包含的函數中。如果一個內部函數依賴於一個外部變數,而該外部變數被外部函數更改,那麼執行內函數就有些困難。舉例,我們在聲明函數之後,但在定義函數之前,改變一個變數的值。[11]
var v, getValue;
v = 1;
getValue = function() { return v; };
v = 2;
getValue(); // 2
當我們手動給v
賦值時這結果似乎沒什麼問題。不過,如果getValue()
是在一個迴圈中被定義的,那麼就可能出現預想外的結果。
var v, getValue;
v = 1;
getValue = (function(x) {
return function() { return x; };
})(v);
v = 2;
getValue(); // 1
此例中,function將 v
作為參數傳入並立即呼叫,保護了內部函數的執行上下文。[12]
David Herman's作品 Effective JavaScript 包含了一個用來在迴圈中求值導致問題的例子。[13] 雖然他的例子刻意編寫得非常複雜,但是原因都是缺乏塊作用域導致的.[14]
利用IIFE建立真正的私有函數和變數,並用閉包訪問
立即呼叫函數表達式也可以用來建立私有方法來訪問函數,不僅起到保護作用,同時也暴露了一些可以後續使用的屬性。[15] 下面的例子來自於 Alman's 關於IIFE的網帖。[1]
// 'counter' 函数返回一个具有属性的对象, 这里的属性就是
// get set等函数
var counter = (function(){
var i = 0;
return {
get: function(){
return i;
},
set: function( val ){
i = val;
},
increment: function() {
return ++i;
}
};
})();
// 这些调用使用了刚才counter得到的属性
counter.get(); // 0
counter.set( 3 );
counter.increment(); // 4
counter.increment(); // 5
如果我們試圖從全域作用域直接訪問 counter.i
,會得到 undefined,因為 i
這個數據由IIFE封裝,它並不是 counter
的屬性。同樣的,如果我們試圖訪問 i
也會收到錯誤,因為 i
並沒有在全域作用域中定義。
術語
"立即呼叫函數表達式" 最早稱為「自執行(匿名)函數」[1][5] 但是立即執行的函數不一定是匿名的。 ECMAScript 5的 strict mode 禁止arguments.callee
,[16] 因此,這個術語不夠準確.[3][12]
在lambda-calculus(λ演算)中,這個構造稱為 "redex", 用來化簡表達式, 參閱:Reduction strategy (code optimization).
參考
- ^ 1.0 1.1 1.2 1.3 Alman, Ben. Immediately Invoked Function Expressions. 2010 [4 February 2013]. (原始內容存檔於2013-01-20).
- ^ Resig, John. Pro JavaScript Techniques. Apress. 2006: 29. ISBN 9781430202837.
- ^ 3.0 3.1 Osmani, Addy. Learning JavaScript Design Patterns. O'Reilly. 2012: 206. ISBN 9781449334871.
- ^ Baagoe, Johannes. Closing parenthesis in function's definition followed by its call. [19 April 2010]. (原始內容存檔於2011-01-22).
- ^ 5.0 5.1 Lindley, Cody. JavaScript Enlightenment. O'Reilly. 2013: 61. ISBN 9781449342883.
- ^ Zakas, Nicholas. Maintainable JavaScript. O'Reilly. 2012: 44. ISBN 9781449327682.
- ^ Crockford, Douglas. Code Conventions for the JavaScript Programming Language. [3 February 2013]. (原始內容存檔於2012-03-05).
- ^ "JavaScript Semicolon Insertion: Everything you need to know (頁面存檔備份,存於互聯網檔案館)", Friday, May 28, 2010
- ^ "Semicolons in JavaScript are optional (頁面存檔備份,存於互聯網檔案館)", by Mislav Marohnić, 07 May 2010
- ^ Haverbeke, Marijn. Eloquent JavaScript. No Starch Press. 2011: 29–30. ISBN 9781593272821.
- ^ Alman, Ben. simple-iife-example.js. Github. [5 February 2013]. (原始內容存檔於2021-04-14).
- ^ 12.0 12.1 Otero, Cesar; Larsen, Rob. Professional jQuery. John Wiley & Sons. 2012: 31. ISBN 9781118222119.
- ^ Herman, David. Effective Javascript. Addison-Wesley. 2012: 44–45. ISBN 9780321812186.
- ^ Zakas, Nicholas C. Mimicking Block Scope. Professional JavaScript for Web Developers. John Wiley & Sons. 2011. ISBN 9781118233092.
- ^ Rettig, Pascal. Professional HTML5 Mobile Game Development. John Wiley & Sons. 2012: 145. ISBN 9781118301333.
- ^ Strict mode. Mozilla JavaScript Reference. Mozilla Developer Network. [4 February 2013]. (原始內容存檔於2013-05-25).
外部連結
- Functions and function scope. Mozilla JavaScript Reference. Mozilla Developer Network. [4 February 2013]. (原始內容存檔於2013-05-25).
- Soshnikov, Dmitry. ECMA-262-3 in detail. Chapter 5. Functions.. [4 February 2013]. (原始內容存檔於2021-02-28).