【译】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

相关文章
相关标签/搜索