(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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章