JavaScript中的當即執行函數表達式

前言

在使用JavaScript的時候常常會看見相似以下的函數調用方式:javascript

(function(){ console.log("test"); })(); 

或者html

(function(){ console.log("test"); }()); 

一些流行的庫也是這樣,好比jQueryjava

(function( window, undefined ) { // code here }) ( window ); 

社區對此種用法的稱呼不盡相同,其中包括「自執行匿名函數」(self-executing anonymous function),「當即執行函數表達式」(Immediately-Invoked Function Expression,如下簡稱IIFE),筆者傾向於第二種叫法。本文淺析一下IIFE是什麼以及爲何要如此用。jquery

當即執行函數表達式

普通的函數聲明與調用方式有如下幾種:express

// 聲明函數f1 function f1() { console.log("f1"); } // 經過()來調用此函數 f1(); // 或者 // 創建匿名函數並賦予變量f2 var f2 = function() { console.log("f2"); } // 經過()來調用此函數 f2(); 

以上都是以顯示的方式聲明函數,而後再進行經過()來進行調用,那麼若是想要直接調用匿名函數呢?能夠作到嗎?試一下:segmentfault

// 直接在匿名函數後邊加()(方式A) function () {console.log("f1");}(); // SyntaxError: Unexpected token ( 

很不幸,出錯了:SyntaxError: Unexpected token (。那麼試試前言中的方法:閉包

// 在匿名函數外面套一個(),而後再用()來調用(方式B) (function(){ console.log("test");})(); // test // 在方式A的外層套一個()(方式C) (function(){ console.log("test");}()); // test 

以上這兩種方式都是能夠正常運行的,若是僅僅到這裏是不夠的,必須要「知其然,知其因此然」,讓咱們看看爲何A不能夠運行,而B和C是能夠運行的。原來,JavaScript解釋器會在默認的狀況下把遇到的function關鍵字看成是函數聲明語句(statement)來進行解釋的,而函數聲明語句是這樣的:ide

function name([param] [, param] [..., param]) { statements } 

A的調用方式明顯是有語法錯誤的,因此纔會拋出異常SyntaxError: Unexpected token (。可是B,C爲何能夠正常運行?這是由於在JavaScript中()之間只能包含表達式(expression),因此在以B,C方式運行的時候,解釋器把()中的內容看成表達式(expression)而不是語句(statement)來執行。爲了可以更好的解釋B,C調用方式的原理,在這裏插入對()操做符的簡單介紹:函數

// 若是傳入字面量(literal),則返回表達式(expression) (1) // 1 (function(){console.log("f");}) // function () {console.log("f")} 

這裏不會對()作更深刻的探討,若是讀者感興趣,能夠自行google。OK,咱們先看B的調用方式:post

(function(){ console.log("test");})(); // test 

因爲把函數的聲明寫在了()之中,因此解釋器以表達式(expression)來解析其中代碼,而根據咱們上面的介紹知道若是向()中傳入函數聲明會直接返回此函數,此步執行完成以後,臨時結果用僞代碼來表示的話,應該相似這樣:

anonymousFunction();

這個就是函數的通用調用方式,因此繼續執行。對於C的調用方式就更加容易理解了,直接把()中的內容看成表達式來進行解釋。

因此根據上面的解釋,咱們知道只要能讓JavaScript解釋器以「函數表達式」而不是「函數聲明」來處理匿名函數的當即執行就能夠了,把語句放在()之中只是其中的一種方法而已,根據這個思路咱們能夠用其餘方式來實現一樣的目的,好比:

// 若是自己就是expression,那麼根本不須要作任何處理 var i = function(){ return 10; }(); true && function(){ /* code */ }(); 0, function(){ /* code */ }(); // 若是你不在意返回值,能夠這麼作 !function(){ /* code */ }(); ~function(){ /* code */ }(); -function(){ /* code */ }(); +function(){ /* code */ }(); // 還有更奇葩的方式,可是不知道性能如何,來自 // http://twitter.com/kuvos/status/18209252090847232 new function(){ /* code */ } new function(){ /* code */ }() 

爲何要用當即執行函數表達式

爲何要用當即執行函數表達式呢?有如下幾個場景。

  • 模擬塊做用域

衆所周知,JavaScript沒有C或Java中的塊做用域(block),只有函數做用域,在同時調用多個庫的狀況下,很容易形成對象或者變量的覆蓋,好比:

liba.js

var num = 1; // code.... 

libb.js

var num = 2; // code.... 

若是在頁面中同時引用liba.jsliba.js兩個庫,必然致使num變量被覆蓋,爲了解決這個問題,能夠經過IIFE來解決:

liba.js

(function(){ var num = 1; // code.... })(); 

libb.js

(function(){ var num = 2; // code.... })(); 

通過改造以後,兩個庫的代碼就徹底獨立,並不會互相影響。

  • 解決閉包衝突

閉包(closure)是JavaScript的一個語言特性,簡單來講就是在函數內部所定義的函數能夠持有外層函數的執行環境,即便在外層函數已經執行完畢的狀況下,在這裏就不詳細介紹了,感興趣的能夠自行Google。咱們這裏只舉一個由閉包引發的最多見的問題:

var f1 = function() { var res = []; var fun = null; for(var i = 0; i < 10; i++) { fun = function() { console.log(i);};//產生閉包 res.push(fun); } return res; } // 會輸出10個10,而不是預期的0 1 2 3 4 5 6 7 8 9 var res = f1(); for(var i = 0; i < res.length; i++) { res[i](); } 

修改爲:

var f1 = function() { var res = []; for(var i = 0; i < 10; i++) { // 添加一個IIFE (function(index) { fun = function() {console.log(index);}; res.push(fun); })(i); } return res; } // 輸出結果爲0 1 2 3 4 5 6 7 8 9 var res = f1(); for(var i = 0; i < res.length; i++) { res[i](); } 
  • 模擬單例

在JavaScript的OOP中,咱們能夠經過IIFE來實現,以下:

var counter = (function(){ var i = 0; return { get: function(){ return i; }, set: function( val ){ i = val; }, increment: function() { return ++i; } }; }()); counter.get(); // 0 counter.set( 3 ); counter.increment(); // 4 counter.increment(); // 5 

總結

本文淺析了JavaScript中的當即執行函數表達式(Immediately-Invoked Function Expression),指出其存在的緣由和其原理,最後舉了經常使用的應用場景,但願對你們有所幫助。

參考:
http://benalman.com/news/2010/11/immediately-invoked-function-expression/#iife
http://blog.coolaj86.com/articles/how-and-why-auto-executing-function.html
http://stackoverflow.com/questions/592396/what-is-the-purpose-of-a-self-executing-function-in-javascript
http://www.cnblogs.com/TomXu/archive/2011/12/31/2289423.html
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function

相關文章
相關標籤/搜索