1、简介
单例模式是JS中最基本但又最有用的模式之一。这种模式提供了一种将代码组织为一个逻辑单位的手段。通过确保单例对象只存在一份实例,你可以确保自己的所有代码使用的都是同样的全局资源。
2、最基本的单例
相对于其他语言,在JS中,创建一个单例无非是太容易了,最简单的单例实际上就是一个对象字面量,它把一批有一定关联的方法和属性组织在一起:
var Singleton = {
attr1: true,
attr2: 10,
method: function(){}
};
在这个例子中,所有成员都可以通过变量Singleton来访问。
Singleton.attr1 = false; var total = Singleton.attr2 + 5; var result = Singleton.method();
这个单例对象的成员可以被修改。这实际上违背了面向对象设计的一条原则:类可以被扩展,但不应该被修改。如果某些变量需要保护,那么可以用到文章(2)中的闭包方式。
并非所有对象字面量都是单体,如果它只是用来模拟关联数组或容纳数据的话,那就显然不是单例。如果它是用来组织一批相关方法和属性的话,那就可能是单例。其区别主要在于设计者的意图。
3、划分命名空间
单例对象由两个部分组成:包含着方法和属性成员的对象自身,以及用于访问它的变量。这个变量通常是全局性的,这是单例模式的一个要点。而单例对象的所有内部成员都被包装在这个对象中,所以它们不是全局性的,这些成员只能通过这个单例对象变量进行访问,因此在某种意义上,可以说它们被单例对象圈在了一个命中空间中。
为了无意中改写变量,最好的解决办法之一是用单例对象将代码组织在命名空间之中。命名空间还可以进一步分隔,可以定义一个用来包含自己的所有代码的全局对象:
/*Util namespace*/
var Util = {};
Util.Event = {
//事件命名空间,包括了事件处理的各种代码
};
Util.Error = {
//存储一些错误处理代码
};
来源于外部的代码与Util这个变量发生冲突的可能性很小。如果真有冲突,其造成的问题会非常明显,所以很容易被发现。也许如果能像其他语言那样用一个 namespace函数或操作符来声明一个命名空间,这样能使得代码更容易组织,且更容易一目了然。当然在JS中可以模拟namespace,但是无法将代码像提供命名空间的语言那样组织在一个大括号中,我们还是必须通过对象和'.'运算符进行显示的成员的添加,不过通过模拟namespace带来的好处就是代码的易读性。以下模拟了namespace:
/*命名空间函数*/
var namespace = function(ns){
if(typeof ns !== 'string'){
throw new Error('namespace must be a string');
}
var ns_arr = ns.split('.'),
s = ns_arr[0], pre_s,
i = 1;
window[s] = window[s] || {};
pre_s = window[s];
for (len = ns_arr.length; i < len; ++i) {
s = ns_arr[i];
pre_s[s] = pre_s[s] || {};
pre_s = pre_s[s];
}
};
/*使用方式*/
namespace('Util.EventUtil');
/*Event Code*/
Util.EventUtil.getTarget = function(){};
4、拥有私有成员的单例
在文章(2)中已经提到,私有成员可以用闭包来实现,因为本身作为单例对象,在全局中只有一份实例存在,所以可以不用担心内存的过大消耗。只需通过一个自运行函数就可很容易的实现拥有私有成员的单例:
var Singleton = function(){
var privateAttr = 0;
return {
getAttr:function(){
return privateAttr;
},
method:function(){
alert('11');
}
};
}();
5、小结
单例模式是JS中最基本的模式之一。把你的代码包装在一个单例中,就不必担心别人在使用它时会改写到他们自己的全局变量,这是向创建大众使用的API这个方向迈出的一大步,也是成为一个值得信赖的高级JavaScirpt程序员所要经历的第一步。