1. 程式人生 > >(function (){})(); JS 閉包 (Closure) 正規化

(function (){})(); JS 閉包 (Closure) 正規化

引言

最常見的閉包 (Closure) 正規化大家都很熟悉了:

(function() {
// ...
})();

很簡單,大家都在用。但是,我們需要了解更多。
首先,閉包是一個匿名函式 (Anonymous function), 即是 (function() {}) 這部分。之所以要給 function 新增括弧是為了讓它形成一個表示式 (expression), 有了表示式,並且確定它的型別是個函式 (Function 例項), 就可以直接呼叫它。所以,後面的一對括弧是可以工作的,它的意義是:我要呼叫 (call) 這個函式。

既然是函式呼叫,那就可以像一般的函式那樣,在呼叫時傳入引數。這就是本次討論的話題。

傳入 window 引數

 

(function(win) {
// ...
})(window);

這樣做最直觀的好處是書寫便利:少寫幾個字。你可以在閉包內任何地方使用 win, 它都會指向 window 物件。另外,它有利於壓縮減少最終程式碼的體積,經過壓縮後 (如

Google Closure Complier), 所有的 win 都會被替換成形如 a 這樣的簡單變數。win 用得越多,減少的位元組數也越多。

不過,便利的同時也會帶來陷阱。在 IE 上,window 總是指向當前視窗物件,這個沒有問題,但是在某些場景下,使用閉包內的 win 變數會導致拒絕訪問錯誤 (Access denied). 重現方式大致是這樣的:當頁面引用其他域名的指令碼,並且該指令碼呼叫了閉包內的 window.document, 而且這個閉包程式碼是來自另一個域名的指令碼。在這種情況下,使用 win 會保持對 window 最早的引用,通過另一個域的指令碼訪問 win 會導致 IE 認為指令碼產生了跨越衝突,從而拒絕了對 win.document 的訪問。解決辦法是不使用形參 win, 而是直接使用 window. 需要說明的是,給閉包傳入 document 也會導致 IE 出現同樣的問題。

傳入 undefined

其實把 undefined 作為形參就,實參就可以不用傳了,因為 JavaScript 中訪問未傳入的引數就會得到 undefined. 因此,你可以這樣寫:

(function(undefined) {
// ...
})();

和上面的討論一樣,你可以在閉包內任何地方使用 undefined, 可以少寫幾個字(如果把 undefined 換成更短的名字),也可以在減少壓縮後體積。

另一個的優勢是,你可以認為它是個變數,把它當變數來使用,它的值恆等於 (===) 真正的 undefined. 當外部程式碼意外地定義了 undefined 的時候——不常見,但確實可能會發生——你可以正常地使用真正的 undefined, 而不會被外部的 undefined 意外影響. 這是由 JavaScript 作用域規則決定的。
無論是否使用這個 undefined 引數,都應該避免使用 undefined 的字串常量,如:

if(typeof myVar === 'undefined') {
// bad part...
}

因為如果你把字串寫錯了,機器不會告訴你,而且會產生一個難以檢查出來的bug. 幸運的是,對於 JavaScript 來說,JsLint 可以幫你做這個校驗。當 myVar 已定義的時候(通過形參或 var 宣告),上面的程式碼改成這樣會更易於除錯:

if(myVar === undefined) {
// good part...
}

結論

從上面兩個例子來看,我們建議不要傳入 window, 但是可以安全地使用第二種方式 (寫 undefined 形參);我們還要儘量避免使用字串常量。
最後,最重要的是,這只是兩個特定物件和型別的討論,舉一反三,你會更瞭解 JavaScript