立即調用函數表達式

立即調用函數表達式(英文: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. ^ 1.0 1.1 1.2 1.3 Alman, Ben. Immediately Invoked Function Expressions. 2010 [4 February 2013]. (原始內容存檔於2013-01-20). 
  2. ^ Resig, John. Pro JavaScript Techniques. Apress. 2006: 29. ISBN 9781430202837. 
  3. ^ 3.0 3.1 Osmani, Addy. Learning JavaScript Design Patterns. O'Reilly. 2012: 206. ISBN 9781449334871. 
  4. ^ Baagoe, Johannes. Closing parenthesis in function's definition followed by its call. [19 April 2010]. (原始內容存檔於2011-01-22). 
  5. ^ 5.0 5.1 Lindley, Cody. JavaScript Enlightenment. O'Reilly. 2013: 61. ISBN 9781449342883. 
  6. ^ Zakas, Nicholas. Maintainable JavaScript. O'Reilly. 2012: 44. ISBN 9781449327682. 
  7. ^ Crockford, Douglas. Code Conventions for the JavaScript Programming Language. [3 February 2013]. (原始內容存檔於2012-03-05). 
  8. ^ "JavaScript Semicolon Insertion: Everything you need to know頁面存檔備份,存於網際網路檔案館)", Friday, May 28, 2010
  9. ^ "Semicolons in JavaScript are optional頁面存檔備份,存於網際網路檔案館)", by Mislav Marohnić, 07 May 2010
  10. ^ Haverbeke, Marijn. Eloquent JavaScript. No Starch Press. 2011: 29–30. ISBN 9781593272821. 
  11. ^ Alman, Ben. simple-iife-example.js. Github. [5 February 2013]. (原始內容存檔於2021-04-14). 
  12. ^ 12.0 12.1 Otero, Cesar; Larsen, Rob. Professional jQuery. John Wiley & Sons. 2012: 31. ISBN 9781118222119. 
  13. ^ Herman, David. Effective Javascript. Addison-Wesley. 2012: 44–45. ISBN 9780321812186. 
  14. ^ Zakas, Nicholas C. Mimicking Block Scope. Professional JavaScript for Web Developers. John Wiley & Sons. 2011. ISBN 9781118233092. 
  15. ^ Rettig, Pascal. Professional HTML5 Mobile Game Development. John Wiley & Sons. 2012: 145. ISBN 9781118301333. 
  16. ^ Strict mode. Mozilla JavaScript Reference. Mozilla Developer Network. [4 February 2013]. (原始內容存檔於2013-05-25). 

外部連結

編輯