立即調用函數(shù)表達式

在使用JavaScript的時候經常會看見類似如下的函數(shù)調用方式:

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

或者

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

一些流行的庫也是這樣蠢挡,比如jQuery

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

社區(qū)對此種用法的稱呼不盡相同,其中包括「自執(zhí)行匿名函數(shù)」(self-executing anonymous function),「立即執(zhí)行函數(shù)表達式」(Immediately-Invoked Function Expression,以下簡稱IIFE),筆者傾向于第二種叫法。
普通的函數(shù)聲明與調用方式有以下幾種:

// 聲明函數(shù)f1
function f1() {
    console.log("f1");
}

// 通過()來調用此函數(shù)
f1();

// 或者
// 建立匿名函數(shù)并賦予變量f2
var f2 = function() {
    console.log("f2");
}

// 通過()來調用此函數(shù)
f2();

以上都是以顯示的方式聲明函數(shù)岩调,然后再進行通過()來進行調用炒俱,那么如果想要直接調用匿名函數(shù)呢盐肃?可以做到嗎?試一下:

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

很不幸权悟,出錯了:SyntaxError: Unexpected token (砸王。那么試試前言中的方法:

// 在匿名函數(shù)外面套一個(),然后再用()來調用(方式B)
(function(){ console.log("test");})(); // test

// 在方式A的外層套一個()(方式C)
(function(){ console.log("test");}()); // test

以上這兩種方式都是可以正常運行的峦阁,如果僅僅到這里是不夠的谦铃,必需要「知其然,知其所以然」榔昔,讓我們看看為什么A不可以運行驹闰,而B和C是可以運行的。原來撒会,JavaScript解釋器會在默認的情況下把遇到的function關鍵字當作是函數(shù)聲明語句(statement)來進行解釋的嘹朗,而函數(shù)聲明語句是這樣的:

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

A的調用方式明顯是有語法錯誤的,所以才會拋出異常SyntaxError: Unexpected token (诵肛。但是B屹培,C為什么可以正常運行?這是因為在JavaScript中()之間只能包含表達式(expression)怔檩,所以在以B褪秀,C方式運行的時候,解釋器把()中的內容當作表達式(expression)而不是語句(statement)來執(zhí)行薛训。為了能夠更好的解釋B溜歪,C調用方式的原理,在這里插入對()操作符的簡單介紹:

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

這里不會對()做更深入的探討蝴猪,如果讀者感興趣调衰,可以自行google。OK自阱,我們先看B的調用方式:

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

由于把函數(shù)的聲明寫在了()之中嚎莉,所以解釋器以表達式(expression)來解析其中代碼,而根據(jù)我們上面的介紹知道如果向()中傳入函數(shù)聲明會直接返回此函數(shù)沛豌,此步執(zhí)行完成之后趋箩,臨時結果用偽代碼來表示的話,應該類似這樣:

anonymousFunction();

這個就是函數(shù)的通用調用方式加派,所以繼續(xù)執(zhí)行叫确。對于C的調用方式就更加容易理解了,直接把()中的內容當作表達式來進行解釋芍锦。

所以根據(jù)上面的解釋竹勉,我們知道只要能讓JavaScript解釋器以「函數(shù)表達式」而不是「函數(shù)聲明」來處理匿名函數(shù)的立即執(zhí)行就可以了,把語句放在()之中只是其中的一種方法而已娄琉,根據(jù)這個思路我們可以用其他方式來實現(xiàn)同樣的目的次乓,比如:

// 如果本身就是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 */ }()

為什么要用立即執(zhí)行函數(shù)表達式

模擬塊作用域

眾所周知女气,JavaScript沒有C或Java中的塊作用域(block)杏慰,只有函數(shù)作用域,在同時調用多個庫的情況下炼鞠,很容易造成對象或者變量的覆蓋缘滥,比如:

liba.js

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

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

如果在頁面中同時引用liba.js和liba.js兩個庫,必然導致num變量被覆蓋簇搅,為了解決這個問題完域,可以通過IIFE來解決:

liba.js

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

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

經過改造之后软吐,兩個庫的代碼就完全獨立瘩将,并不會互相影響。

解決閉包沖突

閉包(closure)是JavaScript的一個語言特性凹耙,簡單來說就是在函數(shù)內部所定義的函數(shù)可以持有外層函數(shù)的執(zhí)行環(huán)境姿现,即使在外層函數(shù)已經執(zhí)行完畢的情況下,在這里就不詳細介紹了肖抱,感興趣的可以自行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來實現(xiàn)提佣,如下:

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
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末吮蛹,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子拌屏,更是在濱河造成了極大的恐慌潮针,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件倚喂,死亡現(xiàn)場離奇詭異每篷,居然都是意外死亡,警方通過查閱死者的電腦和手機端圈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門焦读,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人舱权,你說我怎么就攤上這事矗晃。” “怎么了刑巧?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵喧兄,是天一觀的道長。 經常有香客問我啊楚,道長吠冤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任恭理,我火速辦了婚禮拯辙,結果婚禮上,老公的妹妹穿的比我還像新娘颜价。我一直安慰自己涯保,他們只是感情好,可當我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布周伦。 她就那樣靜靜地躺著夕春,像睡著了一般。 火紅的嫁衣襯著肌膚如雪专挪。 梳的紋絲不亂的頭發(fā)上及志,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天,我揣著相機與錄音寨腔,去河邊找鬼速侈。 笑死,一個胖子當著我的面吹牛迫卢,可吹牛的內容都是我干的倚搬。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼乾蛤,長吁一口氣:“原來是場噩夢啊……” “哼每界!你這毒婦竟也來了捅僵?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤眨层,失蹤者是張志新(化名)和其女友劉穎命咐,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谐岁,經...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡醋奠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了伊佃。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片窜司。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖航揉,靈堂內的尸體忽然破棺而出塞祈,到底是詐尸還是另有隱情,我是刑警寧澤帅涂,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布议薪,位于F島的核電站,受9級特大地震影響媳友,放射性物質發(fā)生泄漏斯议。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一醇锚、第九天 我趴在偏房一處隱蔽的房頂上張望哼御。 院中可真熱鬧,春花似錦焊唬、人聲如沸恋昼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽液肌。三九已至,卻和暖如春鸥滨,著一層夾襖步出監(jiān)牢的瞬間嗦哆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工爵赵, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留吝秕,地道東北人泊脐。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓空幻,卻偏偏與公主長得像,于是被迫代替她去往敵國和親容客。 傳聞我的和親對象是個殘疾皇子秕铛,可洞房花燭夜當晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內容