随着JS的发展,JS程序越写越长,就带来了命名冲突,
因此最重要的convention就是使用命名空间防止两个模块定义了同样名字的全局变量。
另一个重要的convention是引入模块初始化代码,这对浏览器中的JS尤其重要,因为在document加载完毕后操作某个指定document对象是很普遍的事情。
创建模块和命名空间
创建模块最重要的规则是,尽量避免定义全局变量,任何时候你定义了一个全局变量都是冒着可能被其他模块修改的危险。解决不使用全局变量的方法,就是使用自己定义的命名空间。
//Create an empty object as our namespace //This single global symbol will hold all of our other symbols var Class = {}; //Define functions within the namespace Class.define = function(data){ /* code goes here */} Class.provides = function(o,c){ /* code goes here */}
一个模块不要在全局空间中定义超过一个变量
一旦模块在全局命名空间中加入了一个变量,它的文档必须清楚的说明这一点
一旦模块在全局命名空间中加入了一个变量,这个变量的名字和这个模块所在的文件名必须有清晰的关系
一般来讲,模块所在文件和全局变量的名字一致(这样,如果在都遵循这种命名规范的情况下,两个使用同一全局变量的模块是不能共存于同一目录下的),但如果在不同目录下连命名空间名都一样了(唯一的全局变量名一样),前面的一切设计就白费了,为了避免这种情况,将该命名空间所在的目录作为全局变量命名的一部分:
/** * 将文件放置并命名为flanagan/Class.js * * 声明一个唯一的全局变量名为flanagan,并将命名空间设置为flanagan.Class, * 作为flanagan的一个属性。 */ var flanagan; if ( !flanagan) flanagan = {}; //如果这个变量没有用到 flanagan.Class = {} flanagan.Class.define = function(data){....}; flanagan.Class.provides = function(o,c){....};
注意到在上面的代码中,先使用了var操作符,这是因为读取未定义的全局变量会抛出异常,而对于定义了而没初始化的全局变量,它的值只会是undefined。这一现象仅仅对全局变量而言。
可以看出,这里flanagan是一个命名空间的命名空间,如果要更保险,可以使用JAVA的包命名方法
var com;
if (!com) com = {};
else if( typeof com != "object")
throw new Error("com already exists and is not an object");
if( !com.davidflanagan) com.davidflanagan = {};
else if ( typeof com.davidflanagan != "object")
throw new Error("com.davidflanagan already exists....");
if( com.davidflanagan.Class)
throw new Error("com.davidflanagan.Class already exists");
com.davidflanagan.Class = {
define: function(data) {...};
provides: function(o,c){...};
};
上面的例子是一个完整的严格的命名空间检查。
测试模块是否加载:
var com;
if( !com || !com.davidflanagan || !com.davidflanagan.Class)
throw new Error("com/davidflanagan/Class.js has not been loaded")
某些地方还会加入一个版本属性,用来测试指定版本的模块是否加载
在指定命名空间建立一个类:
com.davidflanagan.Complex = com.davidflanagan.Class.define({
name: "Complex",
construct: function(x,y) {this.x = x; this.y = y;}
methods: {
add: function(c){
return new com.davidflanagan.Complex(this.x + c.x,
this.y + c.y);
}
},
});
建立多个类:
com.davidflanagan.shapes = {};
com.davidflanagan.shapes.Circle = define({});
com.davidflanagan.shapes.Rectangle = define({});
com.davidflanagan.shapes.Triangle = define({});
初始化模块代码:
这种代码只需要运行一次,因此使用匿名函数,并让它一旦定义就运行:
(function(){
...//code goes here
...//any variables are safely nested within the function
...//so no global symbols are created.
})();
为了得到唯一的命名空间,命名空间的名字往往冗长,在实际应用中,可以使用以下办法:
var define = com.davidflanagan.Class.define;
注意,引入的必须是函数,对象等类型,如果引入元类型,是没有意义的
另外一点是,引入简化名称,只是对于模块使用者而言,对于模块开发者,应该坚持使用全名
在模块内部,某个变量是内用还是外用,通常的区分方式重点还是在文档,另外是使用闭包
下面的例子创建了一个私有命名空间:
var com;
if(!com) com = {};
if( !com.davidflanagan) com.davidflanagan = {};
com.davidflanagan.Class = {};
(function(){
function define(data){counter++;...};
function provides(o,c){...};
//counter变量将一直处于私有空间
var counter = 0;
function getCounter() {return counter; }
var ns = com.davidflanagan.Class;
ns.define = define;
ns.provides = provides;
ns.getCounter = getCounter;
})();