【譯】JavaScript 命名空間

原文連接:《JavaScript Namespacing》
譯文原鏈:【譯】JavaScript 命名空間javascript

JavaScript 中有不少能夠給你的對象安全分配命名空間的方法。這篇文章討論我見過的廣泛的實踐。java

前綴命名空間

若是命名空間的目的是避免衝突的話。下面這個系統,只要咱們知道全局變量名前綴 myApp_ 是惟一的,能夠像其餘系統同樣避免命名空間衝突。程序員

// add uniquely named global properties
var myApp_sayHello = function() {
  alert('hello');
};
var myApp_sayGoodbye = function() {
  alert('goodbye');
};

// use the namespace properties
myApp_sayHello();

C 語言程序常用前綴命名空間。在 JavaScript 的世界中,你可能會遇見 Macromedia 的 MM_ 方法,例如 MM_showHideLayers。瀏覽器

我認爲前綴命名空間是 JavaScript 中最清楚明白的命名空間系統。(下面的對象命名空間策略在加入了 this 關鍵字後會致使困惑。)安全

前綴命名空間的確建立了不少全局對象。這對於前綴用來避免的命名空間衝突並非什麼問題。前綴命名空間的問題是,有些網頁瀏覽器(例如 IE6)在有不少全局對象時表現很糟糕,就我所據說。我作了一些測試而且發現有一個 comp.lang.javascript 的小線程,不過我沒有就這個話題研究完全。ide

單對象命名空間

當下,最流行的 JavaScript 命名空間實踐是使用一個全局變量來引用一個對象。這個被引用的對象引用你的『真正的業務』,而且由於你的全局對象的命名獨一無二,你的代碼和其餘人的代碼就能夠一塊兒嗨皮地運行。測試

若是你肯定這個世界上沒有任何人用了這個全局變量名 myApp,那麼你能夠有這樣的代碼:網站

// define the namespace object
var myApp = {};

// add properties to the namespace object
myApp.sayHello = function() {
  alert('hello');
};
myApp.sayGoodbye = function() {
  alert('goodbye');
};

// use the namespace properties
myApp.sayHello();

當上面代碼的最後一行執行時,JavaScript 解釋器首先找到 myApp 對象,而後找到並調用這個對象的 syaHello 屬性。this

對象命名空間的一個問題是它會致使與面向對象消息傳遞混淆。這二者之間並無明顯的句法差別:google

// 1
namespace.prop();

// 2
receiver.message();

更仔細地研究這個混淆,咱們得出下面的命名空間想法。假設咱們有如下庫。

var myApp = {};

myApp.message = 'hello';

myApp.sayHello = function() {
  alert(myApp.message);
};

用這個庫的代碼能夠隨意進行寫操做。

myApp.sayHello(); // works
  
var importedfn = myApp.sayHello;

importedfn(); // works

將這個和那個使人混淆的使用 this 的消息傳遞版本比較一下。

var myApp = {};

myApp.message = 'hello';

myApp.sayHello = function() {
  alert(this.message);
};

用這個庫的代碼能夠隨意進行寫操做。

myApp.sayHello() // works because "this" refers to myApp object.

var importedfn = myApp.sayHello;

importedfn(); // error because "this" refers to global object.

這裏面的要上的一課是,this 永遠不能引用一個被做爲命名空間的對象由於它肯能致使關於從命名空間引入標識符的混淆。這個問題是 this 在個人 JavaScript Warning Words 列表中的緣由之一。

(這也代表了庫的 API 屬性應該指向用一個方法,這樣這些方法能夠被導入其餘命名空間。這個問題是在個人文章 Lazy Function Definition Pattern 的評論中被指出的。懶惰方法定義能夠在被隱藏在庫中而且不是 API 的部分時安全使用。)

嵌套對象命名空間

嵌套對象命名空間是另外一個廣泛的實踐,它擴展了對象命名空間的想法。你可能見過相似以下代碼:

YAHOO.util.Event.addListener(/*...*/)

解決上面的代碼須要解釋器首先找到全聚德 YAHOO 對象,而後它的 util 對象,而後它的 Event 對象,而後找到並調用它的 addListener 屬性。這樣的話每次事件處理器綁定到一個 DOM 元素上花的功夫太多了,所以導入的概念開始被採用。

(function() {
  var yue = YAHOO.util.Event;
  yue.addListener(/*...*/);
  yue.addListener(/*...*/);
})();

若是你清楚 YAHOO.util.Event.addListener 方法不會用 this 關鍵字而且永遠引用同一個方法,那麼導入能夠變得更加簡潔。

(function() {
  var yuea = YAHOO.util.Event.addEventListener;
  yuea(/*...*/);
  yuea(/*...*/);
})();

我以爲當目的只是避免標識符衝突時,嵌套對象命名空間的複雜是沒必要要的。難道 Yahoo! 還以爲這些全局標識符 YAHOO_util_Event 和 YAHOO_util_Event_addEventListener 不夠獨特嗎?

我認爲使用嵌套對象命名空間的動機是要看起來和 Java 包命名傳統同樣,這在 Java 中開銷不大。例如,在 Java 中你可能看到以下:

package mytools.text;

class TextComponent {
  /* ... */
}

一個這個類的徹底合格的引用應該是 mytools.text.TextComponent

下面是 Niemeyer 和 Knudsen (寫)的 Learning Java 中包命名的描述:

包名是按層級構成的,使用點分隔的命名傳統。包名組成成分給編譯器和運行系統構成了獨一無二的定位文件的路徑。然而,它們並沒在包之間建立其餘的關係。並無什麼『subpackage』的說法,事實上,包命名空間是直接的,而非層級的。在包層級關係特定部分的包僅僅是由於習慣而有關聯。好比,若是咱們穿件了另外一個叫作 mytools.text.poetry 的包(假設是爲了跟詩有關的一些文字類),這些類並非 mytools.text 包的一部分;它們沒有包成員的訪問權限。

嵌套命名空間的幻覺在 Perl 中也存在。在 Perl 中,嵌套包名由雙冒號分隔開。你能夠看到以下 Perl 代碼:

package Red::Blue;
our $var = 'foo';

一個徹底合格的上述變量引用應該是 $Red::Blue::var

在 Perl 中,就像 Java,命名空間層級的主意只是方便程序員,而不是語言自己要求。Wall,Christiansen 和 Orwant 的 Programming Perl 解釋道:

雙冒號可被用於連接在包名 $Red::Blue::var 中標識符。這意味着 $var 屬於包 Red::Blue。包 Red::Blue 跟可能存在的 Red 包或 Blue 包一點關係都沒有。只是說,Red::BlueRed 或者 Blue 之間的關係可能對於寫代碼或者使用這個程序的人有什麼意義,但跟 Perl 不要緊。(好吧,除了在如今的實現中,符號表 Red::Blue 恰好存在符號表 Red 中。可是 Perl 語言並無直接利用過它。)

上述引用中最後備註暗示了 Perl 可能有和在 JavaScript 中使用嵌套命名空間對象同樣的標識符衝突開銷。若是 Perl 的實現改變了,這個開銷就會消失。在 JavaScript 中,我確定嵌套對象命名空間的開銷永遠不會消失由於 JavaScript 使用延遲綁定。

我並不認爲 JavaScript 中的嵌套對象命名空間提供了任何大好處,不過若是不使用導入的話在運行時可能會開銷很是大。

一個折中方案

若是單純地前綴命名空間在某些瀏覽器中真的很慢,而嵌套命名空間的概念幫助在開發者腦中保持各事務的有序,那我認爲上述 Yahoo! 的例子也能夠這樣寫:

YAHOO.util_Event_addListener

或者用更多的全局名稱:

YAHOO_util_Event.addListener

哪一個維度的命名空間?

Perl 的 CPAN 模塊是基於他們所作的事情進行命名空間管理的。例如,我寫了一個這個命名空間裏的模塊:

JavaScript::Minifier

若是別人用一樣的名字寫他本身的模塊,而且他不自知地經過某些模塊依賴經過同一個名字使用 CPAN 模塊,那麼就會有衝突。

Java 程序員採用最冗長但固然也是最安全的方法。(Java 程序員彷佛都想着在大型系統上運行的代碼。)在 Java 中,包常常是基於誰寫的作什麼的來命名。(myFunc風格的規範化。)『誰寫的』部分甚至使用開發者本身的相對能夠保證惟一性的名字。若是我寫一個 Java 的 minifier,由於我有 michaux.ca 的域名,我可能用如下命名空間:

ca.michaux.javascript.minifier

在 JavaScript 中,通過此次討論,可能這樣寫效率更高:

ca_michaux_javascript_minifier

由於 JavaScript 是以文本的形式服務的,這樣的命名空間可能開銷太大,由於增長了下載時間。Gzip 壓縮會找到公共的字符串並用短字符串替換它們。若是 gzip 不可用的話那麼就能夠考慮使用導入了。

var ca_michaux_javascript_minifier = {};

(function() {
  var cmjm = ca_michaux_javascript_minifier;
  
  // refer to cmjm inside this module pattern
})();

我並非說這些長的命名空間是絕對必須的,不過他們必定是避免命名空間衝突的最安全方法。

其餘命名空間問題

標識符不只在 JavaScript 資源中建立。一個表單的 name 屬性也被加在 document.forms 對象上。像 <form name="myCompany_login"> 這樣命名是有意義的。

命名空間類名屬性,好比 <div class="myCompany_story」>,能夠在減小 CSS 命名空間衝突以及當 JavaScript 代碼在經過類名搜索 DOM 元素時頗有價值。

總結

我我的認爲,任何像 YAHOO.util.Event.addListener 這樣有點或者下劃線的東西都是被衝突嚇傻了的。它能夠就是 YUI.on。Dojo 經過一樣功能的 dojo.connect 提供了足夠的保護,由於它有效地涵蓋了命名空間『誰』和『作什麼』的維度。沒有人會在他們的右腦中會這樣想並在 dojo 命名空間下寫一個 JavaScript 庫。Dojo 的開發人員也不會忘記他們已經有了一個 connect 方法並寫另一個。

若是咱們能有一個網站來讓程序員們保存他們的 JavaScript 全局標識符和下劃線前綴,而且當 ECMAScipt4 發佈了的時候也包括他們的包名,就行了:『JavaScript 命名空間登記處』。

JavaScript 是一個最小概念集的強大語言。即便 JavaScript 並無專門爲避免命名空間衝突設計的語言級支持,仍是有不少解決問題的方法。並無一個『正確』的答案。選一個你最喜歡的。

不過,不管你作什麼,請記住別弄一個另外的全局 $ 標識符。

更多

comp.lang.javascript discussion on namespacing

相關文章
相關標籤/搜索