今天打開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