Javascript 的全局变量和namespace
全局变量的使用应该引起注意,只有系统范围相关的对象才声明为全局变量, 并且避免命名冲突。 比较好的减少全局变量的策略是创建少量的全局对象,这些对象将用作基础模块和子系统的namespace(命名空间)。下面将介绍几种namespace的创建方法。
静态namespace
静态namespace是hard coded, 可以将一个namespace赋值给另一个namespace, 两个namespace将引用相同的对象。
1.直接赋值
直接赋值是最基本的方法,这种方法比较费时费力,如果想重新命名,需要花费一番功夫。 但是这种方法却很安全和明确。方法如下:
var myApp = {}
myApp.id = 0;
myApp.next = function() {
return myApp.id++;
}
myApp.reset = function() {
myApp.id = 0;
}
window.console && console.log(
myApp.next(),
myApp.next(),
myApp.reset(),
myApp.next()
); //0, 1, undefined, 0
通过使用它来引用同级属性,可以使以后的维护更容易,但是这有点冒险,因为没有什么可以阻止重新分配name space的功能:
var myApp = {}
myApp.id = 0;
myApp.next = function() {
return this.id++;
}
myApp.reset = function() {
this.id = 0;
}
myApp.next(); //0
myApp.next(); //1
var getNextId = myApp.next;
getNextId(); //NaN whoops!
2.使用object literal表示法
如果我们只使用一次命名空间,切换命名空间就比较容易,但有时候命名空间的值还是会出乎意料。我们可以假设定义在命名空间构造函数里面的对象的值不会被重新赋值。
var myApp = {
id: 0,
next: function() {
return this.id++;
},
reset: function() {
this.id = 0;
}
}
window.console && console.log(
myApp.next(),
myApp.next(),
myApp.reset(),
myApp.next()
) //0, 1, undefined, 0
3.模块模式
通过函数包装器(通常是自调用)将逻辑与全局范围隔离开,该函数包装器返回表示模块公共接口的对象。 通过立即调用该函数并将结果分配给名称空间变量,我们将模块的API锁定在名称空间中。 此外,未包含在返回值中的所有变量将永远保持私有状态,仅对引用它们的公共函数可见。
var myApp = (function() {
var id= 0;
return {
next: function() {
return id++;
},
reset: function() {
id = 0;
}
};
})();
window.console && console.log(
myApp.next(),
myApp.next(),
myApp.reset(),
myApp.next()
) //0, 1, undefined, 0
像上面的object literal 示例一样,可以很容易地切换接收名称空间,还有其他优点:object literal 是固定的–它全部与属性分配有关,没有空间支持逻辑, 此外,object literal 必须初始化所有属性,并且属性值不能轻易相互引用(因此,内部闭包是不可能的)。 模块模式不受这些限制,并为我们带来了私有性的额外好处。
动态namespace
我们也可以称此部分为namespace注入。 namespace由在函数包装器内直接引用的代理表示–这意味着我们不再需要捆绑返回值来分配给名称空间。 这使名称空间定义更加灵活,并使在独立的名称空间(甚至在全局上下文中)存在模块的多个独立实例变得非常容易。 动态命名空间支持模块模式的所有功能,并具有直观易读的附加优势。
4.提供name space参数
在这里,我们只是将namespace作为参数传递给自调用函数。 id变量是私有的,因为它没有分配给context。
var myApp = {};
(function(context) {
var id = 0;
context.next = function() {
return id++;
};
context.reset = function() {
id = 0;
}
})(myApp);
window.console && console.log(
myApp.next(),
myApp.next(),
myApp.reset(),
myApp.next()
) //0, 1, undefined, 0
我们甚至可以将context设置为全局对象(只需更改一个单词!)。 对于图书馆供应商来说,这是一笔巨大的财富–他们可以将其功能包装在一个自调用功能中,然后由用户决定是否应该是全局的(John Resig在编写JQuery时就是这个概念的早期采用者)
var myApp = {};
(function(context) {
var id = 0;
context.next = function() {
return id++;
};
context.reset = function() {
id = 0;
}
})(this);
window.console && console.log(
next(),
next(),
reset(),
next()
) //0, 1, undefined, 0
5.将this用作namespace代理
詹姆斯·爱德华兹(James Edwards)发表的一篇文章。 “我最喜欢的JavaScript设计模式”显然被许多人误解了,他们认为他最好还是采用模块模式。
模式的优点在于它仅使用设计的语言,仅此而已。 而且,由于名称空间是通过this关键字注入的(在给定的执行上下文中是静态的),因此不能修改它。
var myApp = {};
(function() {
var id = 0;
this.next = function() {
return id++;
};
this.reset = function() {
id = 0;
}
}).apply(myApp);
window.console && console.log(
myApp.next(),
myApp.next(),
myApp.reset(),
myApp.next()
); //0, 1, undefined, 0
更好的是,apply(和call)API提供了上下文和参数的自然分隔–因此,将附加参数传递给模块创建者是非常干净的。 下面的示例演示了这一点,还显示了如何在多个名称空间上独立运行模块:
var subsys1 = {}, subsys2 = {};
var nextIdMod = function(startId) {
var id = startId || 0;
this.next = function() {
return id++;
};
this.reset = function() {
id = 0;
}
};
nextIdMod.call(subsys1);
nextIdMod.call(subsys2,1000);
window.console && console.log(
subsys1.next(),
subsys1.next(),
subsys2.next(),
subsys1.reset(),
subsys2.next(),
subsys1.next()
) //0, 1, 1000, undefined, 1001, 0
当然,如果我们想要一个全局id生成器,那么轻而易举……
nextIdMod();
window.console && console.log(
next(),
next(),
reset(),
next()
) //0, 1, undefined, 0
我们一直以id生成器工具为例,无法充分发挥这种模式的潜力。 通过包装整个库并使用this关键字作为namespace的代表,我们使用户可以轻松地在他们选择的任何context(包括全局context)中运行该库
//library code
var protoQueryMooJo = function() {
//everything
}
//user code
var thirdParty = {};
protoQueryMooJo.apply(thirdParty);
其他注意事项
尽量避免嵌套名称空间。 它们更难维护(对于人和计算机而言),并且会使代码可读性变差。 正如Peter Michaux指出的那样,深层嵌套的名称空间可能是怀旧Java开发人员试图重新创建他们熟悉和喜爱的冗长包链的遗产。
可以跨.js文件跨越单个namespace(尽管只能通过名称空间注入或每个变量的直接分配),但是应谨慎使用依赖项。 此外,将命名空间绑定到文件可以帮助读者更轻松地浏览代码行。
由于JavaScript没有正式的namespace构造,因此存在大量的本地解决方案。 这篇文章仅详细介绍了其中一些。
Further reading
James Edwards: My Favorite JavaScript Design Pattern
Peter Michaux: JavaScript Namespacing