JS當即執行函數表達式(IIFE)

原文爲javascript

http://benalman.com/news/2010/11/immediately-invoked-function-expression/#iifejava

------------------------ES6拓展篇git

----------ES6  let實際上爲 JavaScript 新增了塊級做用域。github

----------塊級做用域的出現,實際上使得得到普遍應用的當即執行函數表達式(IIFE)再也不必要了。express

// IIFE 寫法
(function () {
  var tmp = ...;
  ...
}());

// 塊級做用域寫法
{
  let tmp = ...;
  ...
}

---------------------------------------------------------------------------------------------------------------------------------------------------閉包

 

ps:下文中提到的「當即執行函數」其實就是「當即執行函數表達式」模塊化

  • 問題的核心

如今咱們定義了一個函數(function foo(){}或者var foo = function(){}),函數名後加上一對小括號便可完成對該函數的調用,好比下面的代碼:函數

var foo = function(){ /* code */ };
foo();

接着咱們來看下面的代碼:spa

function(){ /* code */ }(); // SyntaxError: Unexpected token (

報錯了,這是爲什麼?這是由於在javascript代碼解釋時,當遇到function關鍵字時,會默認把它當作是一個函數聲明,而不是函數表達式,若是沒有把它顯視地表達成函數表達式,就報錯了,由於函數聲明須要一個函數名,而上面的代碼中函數沒有函數名。(以上代碼,也正是在執行到第一個左括號(時報錯,由於(前理論上是應該有個函數名的。)debug

  • 一波未平一波又起

有意思的是,若是咱們給它函數名,而後加上()當即調用,一樣也會報錯,而此次報錯緣由卻不相同:

function foo(){ /* code */ }(); // SyntaxError: expected expression, got ')'

爲何會這樣?在一個表達式後面加上括號,表示該表達式當即執行;而若是是在一個語句後面加上括號,該括號徹底和以前的語句不搭嘎,而只是一個分組操做符,用來控制運算中的優先級(小括號裏的先運算)。因此以上代碼等價於:

經過以上的介紹,咱們大概瞭解經過()可使得一個函數表達式當即執行。

有的時候,咱們實際上不須要使用()使之變成一個函數表達式,啥意思?好比下面這行代碼,其實不加上()也不會保錯:

var i = function(){ return 10; }();

可是咱們依然推薦加上():

var i = (function(){ return 10; }());

爲何?由於咱們在閱讀代碼的時候,若是function內部代碼量龐大,咱們不得不滾動到最後去查看function(){}後是否帶有()來肯定i值是個function仍是function內部的返回值。因此爲了代碼的可讀性,請儘可能加上()不管是否已是表達式。

  • 當即執行函數與閉包的曖昧關係

當即執行函數能配合閉包保存狀態。

像普通的函數傳參同樣,當即執行函數也能傳參數。若是在函數內部再定義一個函數,而裏面的那個函數能引用外部的變量和參數(閉包),利用這一點,咱們能使用當即執行函數鎖住變量保存狀態。

// 並不會像你想象那樣的執行,由於i的值沒有被鎖住
// 當咱們點擊連接的時候,其實for循環已經執行完了
// 因而在點擊的時候i的值其實已是elems.length了
var elems = document.getElementsByTagName( 'a' );
 
for ( var i = 0; i < elems.length; i++ ) {
 
  elems[ i ].addEventListener( 'click', function(e){
    e.preventDefault();
    alert( 'I am link #' + i );
  }, 'false' );
 
}
 
// 此次咱們獲得了想要的結果
// 由於在當即執行函數內部,i的值傳給了lockedIndex,而且被鎖在內存中
// 儘管for循環結束後i的值已經改變,可是當即執行函數內部lockedIndex的值並不會改變
var elems = document.getElementsByTagName( 'a' );
 
for ( var i = 0; i < elems.length; i++ ) {
 
  (function( lockedInIndex ){
 
    elems[ i ].addEventListener( 'click', function(e){
      e.preventDefault();
      alert( 'I am link #' + lockedInIndex );
    }, 'false' );
 
  })( i );
 
}
 
// 你也能夠這樣,可是毫無疑問上面的代碼更具備可讀性
var elems = document.getElementsByTagName( 'a' );
 
for ( var i = 0; i < elems.length; i++ ) {
 
  elems[ i ].addEventListener( 'click', (function( lockedInIndex ){
    return function(e){
      e.preventDefault();
      alert( 'I am link #' + lockedInIndex );
    };
  })( i ), 'false' );
 
}

其實上面代碼的lockedIndex也能夠換成i,由於兩個i是在不一樣的做用域裏,因此不會互相干擾,可是寫成不一樣的名字更好解釋。以上即是當即執行函數+閉包的做用。

  • 我爲何更願意稱它是「當即執行函數」而不是「自執行函數」

IIFE的稱謂在如今彷佛已經獲得了普遍推廣(不知道是否是原文做者的功勞?),而原文寫於10年,彷佛當時流行的稱呼是自執行函數(Self-executing anonymous function),接下去做者開始爲了說明當即執行函數的稱呼好於自執行函數的稱呼開始力排衆議,有點咬文嚼字,不過也蠻有意思的,咱們來看看做者說了些什麼。

// 這是一個自執行函數,函數內部執行的是本身,遞歸調用
function foo() { foo(); }
 
// 這是一個自執行匿名函數,由於它沒有函數名
// 因此若是要遞歸調用本身的話必須用arguments.callee
var foo = function() { arguments.callee(); };
 
// 這可能也算是個自執行匿名函數,但僅僅是foo標誌引用它自身
// 若是你將foo改變成其它的,你將獲得一個used-to-self-execute匿名函數
var foo = function() { foo(); };
 
// 有些人叫它自執行匿名函數,儘管它沒有執行本身,只是當即執行而已
(function(){ /* code */ }());
 
// 給函數表達式添加了標誌名稱,能夠方便debug
// 可是一旦添加了標誌名稱,這個函數就再也不是匿名的了
(function foo(){ /* code */ }());
 
// 當即執行函數也能夠自執行,不過不經常使用罷了
(function(){ arguments.callee(); }());
(function foo(){ foo(); }());

個人理解是做者認爲自執行函數是函數內部調用本身(遞歸調用),而當即執行函數就如字面意思,該函數當即執行便可。其實如今也不用去管它了,就叫IIFE好了。

  • 最後的旁白:模塊模式

當即執行函數在模塊化中也大有用處。用當即執行函數處理模塊化能夠減小全局變量形成的空間污染,構造更多的私有變量。

// 建立一個當即執行的匿名函數
// 該函數返回一個對象,包含你要暴露的屬性
// 以下代碼若是不使用當即執行函數,就會多一個屬性i
// 若是有了屬性i,咱們就能調用counter.i改變i的值
// 對咱們來講這種不肯定的因素越少越好
 
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並非counter的屬性
i; // ReferenceError: i is not defined (函數內部的是局部變量)
相關文章
相關標籤/搜索