JavaScript 命名空间






4.33/5 (9投票s)
通过函数创建命名空间。
引言
来自 C# 或 Java 背景的开发者都知道命名空间的重要性。它是保持代码组织的主要方式。JavaScript 本身并不支持命名空间,或者说,它真的不支持吗?
对象嵌套
JavaScript 是一种动态语言。你可以动态地创建对象,并且对象可以包含其他对象。这个概念允许你创建一个原型结构,从而模拟命名空间。
让我们看一个哺乳动物、猫和狗的例子
var Animal=function() {
//we don't know yet
this.numberOfLegs=null;
this.sound=null;
return this;
}
Animal.prototype.getNumberOfLegs=function() {
return this.numberOfLegs;
}
Animal.prototype.makeSound=function() {
alert(this.sound);
}
var Mammal=function() {
//all mammals have for legs;
this.numberOfLegs=4;
return this;
}
Mammal.prototype=new Animal();
var Dog=function() {
this.sound="Woef";
}
Dog.prototype=new Mammal();
var Cat=function() {
this.sound="Miauw";
return this;
}
Cat.prototype=new Mammal();
var Tiger=function() {
this.sound="Wroaar";
return this;
}
Tiger.prototype=new Mammal();
var Leopard=function() {
this.sound="Grrrr";
return this;
}
Leopard.prototype=new Mammal();
这很好。我们可以创建所有的哺乳动物,还可以为鸟类等创建基类... 但是,如果我们还需要维护一个 iOS 名称列表呢?要列出苹果的 iOS 版本,我们需要 Tiger 和 Leopard。
此外,我们应该将 Animal
视为不应直接使用的类。我倾向于将这类类称为 Base。但是,如果没有命名空间,我只能有一个 Base 类。
总而言之,我们希望将我们的动物放在 Animals
命名空间中。允许有一个 iOS 命名空间,其中也包含 Tiger 和 Leopard,以及一个 Base 类。我们欺骗 JS 模拟命名空间的方法是创建嵌套对象,并在其中放置其他对象。
//Create the Animals namespace
if(typeof Animals=='undefined') {
window["Animals"]={};
}
Animals.Base=function() {
//we don't know yet
this.numberOfLegs=null;
this.sound=null;
return this;
}
Animals.Base.prototype.getNumberOfLegs=function() {
return this.numberOfLegs;
}
Animals.Base.prototype.makeSound=function() {
alert(this.sound);
}
Animals.Mammal=function() {
//all mammals have for legs;
this.numberOfLegs=4;
return this;
}
Animals.Mammal.prototype=new Animals.Base();
Animals.Dog=function() {
this.sound="Woef";
return this;
}
Animals.Dog.prototype=new Mammal();
//...etc
注意省略了 'var
'。我们所做的是将所有内容添加到全局命名空间中。有些人认为这是绝对禁止的,但 jQuery、ExtJs、YUI 和 prototype 都是这样做的。事实是,如果你将所有类甚至函数放在一个命名空间中,比如你的产品名称,那么实际上可以防止污染全局命名空间。
除此之外,我倾向于将所有代码包装在一个匿名函数内的 .js 文件中。这样,你就可以拥有只在该源文件中可用的“全局”变量,并且它们不会污染全局命名空间。
(function() {
//Create OurProduct namespace
if(typeof OurProduct=='undefined') {
window["OurProduct"]={};
}
var someVariableAvailableToAllClassesWithinThisFunctionButNotOutside="Hello";
//Create the Animals namespace inside the OurProduct namespace
if(typeof OurProduct.Animals=='undefined') {
window["OurProduct"]["Animals"]={};
}
OurProduct.Animals.Base=function() {
//we don't know yet
this.numberOfLegs=null;
this.sound=null;
return this;
}
.......
})();
//Note the () at the end. If you omit that this code will never be run.
注册命名空间
以这种方式进行命名空间实际上只是创建嵌套对象。由于 window 本身就是一个对象,因此你可以直接在其中创建对象。但是,如果我们需要在一个不存在的嵌套命名空间中创建一个对象,那么我们需要先创建它。
MyProduct.Objects.Person=function() {
//This will fail, because MyProduct.Objects doesn't yet exist.
}
我们希望有一个像 NameSpace.register("MyProduct.Object")
这样的函数。所以,让我们创建它。
(function() {
Namespace = {
register : function(ns) {
//Split the string
var nsParts=ns.split(".");
//Store window in obj
var obj=window;
//Travese through the string parts
for(var i=0, j=nsParts.length; i<j; i++) {
if(typeof obj[nsParts[i]] == "undefined") {
//Is it did not exist create it and copy it to obj
//so the next part can be copied into this part.
obj=obj[nsParts[i]]={};
}
else {
//Still copy it to obj, cause the next one might not exist
obj=obj[nsParts[i]];
}
}
}
}
NameSpace.register("MyProduct.Objects");
MyProduct.Objects.Person=function() {
alert("yeee this is legal")
}
var P=new MyProduct.Objects.Person();
})();
这正在取得进展。这个函数已经在我的项目中使用了多年。直到我开始思考,“既然我总是将类包装在匿名函数中,为什么不让这个函数更有用呢?” 此外,我的 C# 代码看起来像这样
namespace MyProduct.Objects {
public class Person {
....
}
}
当然,我们不能也不应该试图让代码看起来完全一样,但我们可以拥有一些非常相似的东西。
Namespace("MyProject.Objects", function() {
MyProduct.Objects.Person=function() {
....
}
});
//notice the omission of the ()
//starting the function will be a task of the namespace function
那么我们该如何实现呢?让我们创建命名空间函数
//This function itself is'nt Namespaced so it should be wrapped in an anonymous function
(function() {
//no var so it will end up in the global namespace
Namespace=function(ns, fs) {
//ns Namespace as . seperated string
//fs the return function
//Here's our trusty register function, only now it's inline
var register = function(ns) {
//Split the string
var nsParts=ns.split(".");
//Store window in obj
var obj=window;
//Travese through the string parts
for(var i=0, j=nsParts.length; i<j; i++) {
if(typeof obj[nsParts[i]] == "undefined") {
//Is it did not exist create it and copy it to obj
//so the next part can be copied into this part.
obj=obj[nsParts[i]]={};
}
else {
//Still copy it to obj, 'cause the next one might not exist
obj=obj[nsParts[i]];
}
}
}
//Let's register the namespace
register(ns);
//And call the wrapper function.
fs();
};
})();
我使用大写字母作为命名空间的首字母。通常我会用小写字母开始一个函数。但是,例如,class 这个词是一个保留字。所以我担心 namespace 可能会在未来成为一个保留字。
结论
许多人认为 JavaScript 不是一种“真正的”语言。但无论你的观点如何,这都不是随意编写长函数链和页面代码中 if-then-else 语句的理由。一旦你遇到任何可能被认为具有状态并且可能随时间变化的东西,你就需要开始考虑为它创建对象。一旦你这样做,你就需要一个命名空间来放置它们。起初这可能看起来很荒谬,但随着你的代码库越来越大,它会更有意义。