javascript实现命名空间
在小规模的脚本开发中,有时候并不值得去引用命名空间,因为会带来某种程度的复杂性;但是当在同一个网页里引入10多个js文件之后,各js中的同名函数就很容易冲突了。比如xxx库里写了个addCssStyle方法,yyy类库里也写了个addCssStyle方法,而这两个方法的具体实现又有一定差别。那么同时引用这两个组件的时候,函数冲突之后导致页面效果发生变化,调试和修改都是非常痛苦的,如果为了避免冲突,而放弃引用一些优秀的组件,那更是让人郁闷的事情。为此,在封装javascript组件库的时候,请使用命名空间来避免冲突。将所有的方法和变量都要按包名类名的方式来写。(这个时候写代码的感觉和封装java的util方法一样方便,呵呵)
所以,请记住:请为你封装的JavaScript库加上命名空间以提高代码重用性。
但是JavaScript原生并不支持命名空间,需要变通来实现。
在JavaScript中,所有的对象(或者称类型,例如Boolean,Array,Function)都可以认为是一个关联数组。关联数组中的对象可以使用点(.)进行引用,这样我们可以利用关联数组变相地实现命名空间。首先声明一个关联数组作为根,因为页面声明的对象都是window这个变量的成员,所以一般命名空间的实现都以window为根。当向window申请a.b.c的命名空间时,首先在window中查看是否存在a这个成员,如果没有则在window下新建一个名为a的空关联数组,如果已经存在a,则继续在window.a中查看b是否存在,以此类推。下面分别是Atlas和YUI中的实现方法。
//Atlas命名空间的实现方法 Function.registerNamespace =function(namespacePath){ //以window为根 var rootObject =window; //对命名空间路径拆分成数组 var namespaceParts =namespacePath.split('.'); for (var i =0;i <namespaceParts.length;i++) { var currentPart =namespaceParts[i]; //如果当前命名空间下不存在,则新建一个Object对象,等效于一个关联数组。 if (!rootObject[currentPart]) { rootObject[currentPart]=new Object(); } rootObject =rootObject[currentPart]; } }
Atlas的实现通俗易懂。Javascrip中rootObject[currentPart]=new Object();和rootObject[currentPart]={};是等效的两种写法。
//YUI命名空间的实现方法 var YAHOO = window.YAHOO || {}; YAHOO.namespace = function(ns) { if (!ns || !ns.length) { return null; } var levels = ns.split("."); var nsobj = YAHOO; //如果申请的命名空间是在YAHOO下的,则必须忽略它,否则就成了YAHOO.YAHOO了 for (var i=(levels[0] == "YAHOO") ? 1 : 0; i<levels.length; ++i) { //如果当前命名空间下不存在,则新建一个关联数组。 nsobj[levels[i]] = nsobj[levels[i]] || {}; nsobj = nsobj[levels[i]]; } //返回所申请命名空间的一个引用; return nsobj; };
YUI的实现带有一点C风格,nsobj[levels[i]] = nsobj[levels[i]] || {};这句相比于Atlas显得要晦涩一些。
比较一下Atlas和YUI的实现,YUI稍微好一些,因为YUI中申请命名空间的时候会返回一个引用,可以赋值给一个变量,这就相当于声明了该名称空间的一个别名,编码会方便不少。YUI把所有申请的命名空间都放在了window.YAHOO下面,这样有什么好处呢?假如Yahoo和其他公司有合作关系,需要嵌入对方的脚本时,这样能保证它自己编写的代码都在YAHOO这个空间下面,而其他公司不大可能在这个空间下面编码,就基本不会出现命名冲突的情况。我觉得这个做法好,是因为在不对等的合作关系中,要求对方去修改代码来适应你的应用是不现实的,YUI的实现考虑了这点。
还有一点,所有的命名空间都放在window.YAHOO下是最好最合理的做法嘛?在大量应用动态效果的页面中,这势必要维护一个超大的关联数组。关联数组本质是哈希数组,检索数组成员的开销可以忽略不计,但是window.YAHOO承载的东西太多太复杂,这是否违背了大道至简的原则?有没有更好的办法?
例子:
//1、命名空间注册工具类 var Namespace = new Object(); Namespace.register = function(path) { var arr = path.split("."); var ns = ""; for ( var i = 0; i < arr.length; i++) { if (i > 0) ns += "."; ns += arr[i]; eval("if(typeof(" + ns + ") == 'undefined') " + ns + " = new Object();"); } } //2、注册命名空间 com.cjm.ui Namespace.register("com.cjm.ui"); //3、使用命名空间 com.cjm.ui.TreeGrid = function() { this.sayHello = function(name) { alert("Hello " + name); } } var t = new com.cjm.ui.TreeGrid(); t.sayHello("uid");