在JavaScript中,单例模式(即实例结构模式)是最基本、最有用的模式之一。这种模式提供了一种将代码组织为一个逻辑单元的手段,这个逻辑单元中的代码可以通过单一的变量进行访问。确保单例对象只有一份实例,就可以确信自己的所有代码使用的都是同样的全局资源。单例类在JavaScript中用途广泛:
❑可以用来划分命名空间,以减少全局变量的数量。
❑可以在一种名为分支的技术中用来封装浏览器之间的差异。
❑可以借助于单例模式,将代码组织得更为一致,从而使代码更容易阅读和维护。
1.第一种模式:对象直接量
最基本的单例实际上是一个对象字面量,它将一批有一定关联的方法和属性组织在一起。
var Singleton={
attribute1:true;
attribute2:10
method1:function{},
method2:function{}
};
这些成员可以通过Singleton加圆点运算符来访问,例如:
Singleton.attribute1=false;
var total=Singleton.attribute2+5;
var result=Singleton.method1;
对象字面量只是用于创建单例的方法之一,并非所有对象字面值都是单体,如果它只是用来模仿关联数组或容纳数据的话,那显然不是单例。但如果它用来组织一批相关方法和属性,那就可能是单例,其区别主要在于设计者的意图。
在单例对象内创建类的私有成员的最简单、最直接的方法是使用下画线表示法。在JavaScript业界,如果变量和方法使用下画线,则表示该变量和方法是私有方法,只允许内部调用,第三方不应该去调用。
GiantCorp.DataParser={
//私有方法
_stripWhitespace:function(str){
return str.replace(//s+/,'');
},
//公用方法
stringToArray:function(str,delimiter,stripWS){
if(stripWS){
str=this._stripWhitespace(str);
}
var outputArray=this._stringSplit(str,delimiter);
return outputArray;
}
};
在stringToArray方法中使用this访问单体中的其他方法,这是访问单体中其他成员或方法最简便的方式。这样做有一点风险,因为this并不一定指向GiantCorp.DataParser。例如,如果把某个方法用做事件监听器,那么其中的this指向的是window对象,因此大多数JavaScript库都会为事件关联进行作用域校正,如在这里使用GiantCorp.DataParser比使用this更为安全。
2.第二种模式:使用闭包
在单例对象中创建私有成员的第二种方法是借助闭包。因为单例只会被实例化一次,所以不必担心自己在构造函数中声明了多少成员。由于每个方法和属性都只会被创建一次,所以可以把它们声明在构造函数内部。使用闭包创建拥有私有成员的单例类的示例如下:
MyNamespace.Singleton=(function{
//私有成员
var privateAttribute1=false;
function privateMethod1{}
return{
//公有成员
publicAttribute1:true;
publicMethod1:function{},
};
});
这种单例模式又称为模块模式,指的是它可以把一批相关方法和属性组织为模块并起到划分命名空间的作用。将私有成员放在闭包中可以确保其不会在单例对象之外被使用,因此开发人员可以自由地改变对象的实现细节,而不会殃及别人的代码。还可以使用这种办法对数据进行保护和封装。
在JavaScript中,使用单例模式的主要优点如下:
❑对代码的组织作用。单例模式将相关方法和属性组织在一个不会被多次实例化的单例中,可以使代码的调试和维护变得更轻松。描述性的命名空间还可以增强代码的自我说明性。将方法包裹在单例中,可以防止它们被其他程序员误改。
❑单例模式的一些高级变体可以在开发周期的后期用于对脚本进行优化。
在JavaScript中,使用单例模式的主要缺点如下:
❑因为提供的是一种单点访问,所以它有可能导致模块间的强耦合。单体最好是留给定义命名空间和实现分支型方法使用,在这些情况下耦合不是什么问题。
❑有某些更高级的模式会更符合任务的需要。虚拟代理能给予开发人员对实例化方式更多的控制权。也可以用一个真正的对象工厂来取代分支型单例。