今天開啟JQuery原始檔(jquery-1.8.3), 看到JQuery的初始化過程是這樣的
(function( window, undefined ) { // .... })( window );
一開始看不懂這個寫法, 經過幾番搜尋終於明白它的用法以及為什麼這樣用了, 我們一步步來分析.
1, 首先我們簡化這個寫法
除去引數, 經過簡化後的寫法可以寫成
(function(){ console.log("Hello World"); })();
後面都使用這個寫法作為示例.
2, 函式宣告與函式表示式
網上有許多介紹建立JavaScript函式的文章告訴我們建立JavaScript函式有兩種方式: 函式宣告與函式表示式.
//function definition (also called function declaration) function func1() { console.log("Hello World1"); } func1(); // function expression var func2 = function (){ console.log("Hello World2"); }; func2();
第一種方式"函式定義"是標準的函式定義方式, 對函式func1的呼叫可以出現在函式定義之前;
第二種被稱為"函式表示式", 與函式定義不同的是對函式func2的呼叫必須出現在變數func2之後的, 因為變數func2本質上是一個指向函式物件的變數, 這與我們定義普通變數的方式本質上是一樣的; 另外一點是通過函式表示式建立的函式名可以不寫, 我們稱之為匿名函式.比如
var a = 10; var f = function(){//code...};
f(); // 呼叫函式f
這裡我們將變數f指向賦值運算子右邊所建立的匿名函式, 然後就可以通過f()直接去呼叫這個匿名函式了, 那麼, 問題來了, 我們是不是可以直接在建立好匿名函式之後就立即呼叫而不去多此一舉賦值給變數f呢?
我們試試
function(){console.log("Hello World3");}(); // output: Uncaught SyntaxError: Unexpected token (
很遺憾報錯, 為什麼會這樣呢? 這時我們再回過頭來看看函式定義與函式表示式的語法區別.
函式定義: function funcName(){//code...} funcName();
函式表示式: function [funcName](){//code...} funcName();
兩者的語法差別很小, 因此當JavaScript直譯器解釋到function關鍵字是是把這段程式碼當做函式定義呢還是函式表示式呢? 根據JavaScript語法, 以function開始的語句會被當做函式定義, 而函式定義是必須要有函式名的, 並且通過函式名來執行函式, 但是顯然function(){};()是不符合這個語法規範的, 這也就解釋了為什麼會報錯. 所以, 任何可以使得JavaScript直譯器把這一語句解釋為函式表示式的方法都應該能讓這一句程式碼成功執行. 那麼問題又來了: 如何實現這一目的呢?
var a = 1;
這就是一個簡單的表示式(expression), 因此我們想到可以在這一語句前面加上一個合適的運算子, 在這裡由於運算子右邊只能有一個函式物件運算元(JavaScript語句), 所以我們應該用運算元可以為物件的(一元)運算子, 我們來試試各種運算子.
! function(){console.log("Hello World2");}(); //Hello World2 + function(){console.log("Hello World3");}(); //Hello World3 - function(){console.log("Hello World4");}(); //Hello World4 delete function(){console.log("Hello World5");}(); //Hello World5 void function(){console.log("Hello World6");}(); //Hello World6
這些運算子都能實現我們的目的, 即讓JavaScript直譯器以建立函式表示式的方式建立這個函式. 至於具體使用哪一個運算子可以自己決定, 不過很明顯我們希望用最簡潔的方式. 在實踐中一些大牛傾向於使用"!", 這一點在stackoverflow中有非常多的討論. http://stackoverflow.com/questions/3755606/what-does-the-exclamation-mark-do-before-the-function
3, 圓括號Parenthesis
另外一種更常用的寫法, 也就是JQuery的用法, 是用圓括號作為分組操作符來讓該執行函式語句被強制解釋為以函式表示式的方式來建立這個匿名函式.
// 第一種寫法 ()分組運算子的內部程式碼只能是表示式, 這裡將以函式表示式的方式建立並返回匿名函式 (function(){/* code... */ };)(); // 第二種寫法 直接將最頂層的括號內部當做表示式建立並執行該匿名函式 (function(){...}(););
(function(){console.log("Hello World6");})(); //Hello World6
(function(){console.log("Hello World6");}()); //Hello World6
4, 結論
分析下來, 其實這樣寫的目的很簡單: 就是定義一個匿名函式並執行. 至於為什麼這樣寫, 這是利用閉包closure的特性來初始化全域性變數, 將這些全域性變數的scope控制在匿名函式內部. 至於閉包, 下次再扯吧, 先下班了.
參考資料
1, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions
2, http://www.zhihu.com/question/20292224
3, http://www.zhcexo.com/round-brackets-in-javascript/
4, http://swordair.com/function-and-exclamation-mark/