JavaScript实现设计模式中的单例模式的一些技巧总结
一、使用全局变量保存单例
这是最简单的实现方法
functionPerson(){
this.createTime=newDate();
}
varinstance=newPerson();
functiongetInstance(){
returninstance;
}
加载该js时就创建一个Person对象,保存到instance全局变量中,每次使用都取这个对象。如果一次都没使用,那么创建的这个对象则浪费了,我们可以优化一下,
varinstance
functiongetInstance(){
if(!instance){
instance=newPerson();
}
returninstance;
}
这样,第一次使用时才创建对象。
这个方法的缺点是,instance是全局的变量,在多人合作或者开发周期比较长的情况下,很难保证instance不会被其它代码修改或覆盖,很可能到调用的时候,发现instance根本就不是Person对象。
我们考虑下使用闭包来封装起instance,使它不再是全局变量就可以解决这个问题了
二、闭包创建对象
vargetInstance(){
varinstance;
returnfunction(){
if(!instance){
instance=newPerson();
}
returninstance;
}
}();
这样,instance就被封装起来了,不用担心被修改了。
现在通过getInstance()函数可以获得单例了。新的问题,如果我通过newPerson()来创建对象,获得的还是多个对象,javascript又不可以像java一样把构造器私有化。那怎么样可以让多次new出来的对象都是一个实例呢?
三、构造函数的静态属性缓存实例
先看代码
functionPerson(){
//如果已经缓存了实例,则直接返回缓存的实例
if(typeofPerson.instance==='object'){
returnPerson.instance;
}
this.createTime=newDate();
//缓存实例
Person.instance=this;
returnthis;
}
从代码可以看到,第一次new时,if的条件返回false,会往下走,初始化对象,然后保存对象到Person.instance这个静态属性中。
第二次new时,if的条件返回true,直接返回Person.instance,不会再往下运行初始化的代码。所以不管new几次,返回的都是第一次创建的对象。
这个方法的缺点和方法一的缺点一样,Person.instance也是公开属性,有可能会被修改。
我们参考方法二,使用闭包来封装一个,也许就能解决该问题了
四、重写构造函数
这个方法要使用闭包,但不能像方法二那么简单,我们需要重写构造函数。
functionPerson(){
//缓存实例
varinstance=this;
this.createTime=newDate();
//重写构造函数
Person=function(){
returninstance;
}
}
第一次new时,调用原始构造函数先缓存该实例,然后再初始化,同时,重写该构造函数。以后再new时,永远调用不到原始的构造函数了,只能调用到重写后的构造函数,而这个函数总是返回缓存的instance.
上面的方法似乎没什么问题,但通过下面的测试,可以发现问题
//向原型添加属性 Person.prototype.prop1=true; varp1=newPerson(); //在创建初始化对象后,再次向该原型添加属性 Person.prototype.prop2=true; varp2=newPerson(); //开始测试 console.log(p1.prop1);//结果为true console.log(p2.prop1);//结果为true console.log(p1.prop2);//结果为undefined console.log(p2.prop2);//结果为undefined console.log(p1.constructor===Person);//结果为false console.log(p2.constructor===Person);//结果为false
我们预期中的结果,应该是全都是true。
分析一下上述测试代码
Person.prototype.prop1=true;是在原始构造函数的原型下增加了prop1这个属性,并赋值
而在执行varp1=newPerson();之后,Person这个构造函数已经被重写了
所以Person.prototype.prop2=true;是在新的原型下增加prop2这个属性
varp2=newPerson();p2和p1实际上是同一个对象,即原始构造函数创建的对象
所以p1p2都有prop1这个属性,而没有prop2这个属性
同样的,p1p2的constructor指向的也是原始的构造函数,而Person此时已不是原来那个函数了
为了能按预期的结果那样运行,可以通过一些修改来实现
functionPerson(){
//缓存实例
varinstance=this;
//重写构造函数
Person=function(){
returninstance;
}
//保留原型属性
Person.prototype=this;
//实例
instance=newPerson();
//重置构造函数引用
instance.constructor=Person;
//其他初始化
instance.createTime=newDate();
returninstance;
}
再运行前面的测试代码,结果都是true了。
五、惰性加载:
在大型或复杂的项目中,起到了优化的作用:那些开销较大却很少用到的组件可以被包装到惰性加载单例中,示例程序:
/*SingletonwithPrivateMembers,step3.*/
MyNamespace.Singleton=(function(){
//Privatemembers.
varprivateAttribute1=false;
varprivateAttribute2=[1,2,3];
functionprivateMethod1(){
...
}
functionprivateMethod2(args){
...
}
return{//Publicmembers.
publicAttribute1:true,
publicAttribute2:10,
publicMethod1:function(){
...
},
publicMethod2:function(args){
...
}
};
})();
/*Generalskeletonforalazyloadingsingleton,step1.*/
MyNamespace.Singleton=(function(){
functionconstructor(){//Allofthenormalsingletoncodegoeshere.
//Privatemembers.
varprivateAttribute1=false;
varprivateAttribute2=[1,2,3];
functionprivateMethod1(){
...
}
functionprivateMethod2(args){
...
}
return{//Publicmembers.
publicAttribute1:true,
publicAttribute2:10,
publicMethod1:function(){
...
},
publicMethod2:function(args){
...
}
}
}
})();
/*Generalskeletonforalazyloadingsingleton,step2.*/
MyNamespace.Singleton=(function(){
functionconstructor(){//Allofthenormalsingletoncodegoeshere.
...
}
return{
getInstance:function(){
//Controlcodegoeshere.
}
}
})();
/*Generalskeletonforalazyloadingsingleton,step3.*/
MyNamespace.Singleton=(function(){
varuniqueInstance;//Privateattributethatholdsthesingleinstance.
functionconstructor(){//Allofthenormalsingletoncodegoeshere.
...
}
return{
getInstance:function(){
if(!uniqueInstance){//Instantiateonlyiftheinstancedoesn'texist.
uniqueInstance=constructor();
}
returnuniqueInstance;
}
}
})();
六、使用分支单例:
针对特定环境的代码可以被包装到分支型单例中,示例程序:
/*SimpleXhrFactorysingleton,step1.*/
varSimpleXhrFactory=(function(){
//Thethreebranches.
varstandard={
createXhrObject:function(){
returnnewXMLHttpRequest();
}
};
varactiveXNew={
createXhrObject:function(){
returnnewActiveXObject('Msxml2.XMLHTTP');
}
};
varactiveXOld={
createXhrObject:function(){
returnnewActiveXObject('Microsoft.XMLHTTP');
}
};
})();
/*SimpleXhrFactorysingleton,step2.*/
varSimpleXhrFactory=(function(){
//Thethreebranches.
varstandard={
createXhrObject:function(){
returnnewXMLHttpRequest();
}
};
varactiveXNew={
createXhrObject:function(){
returnnewActiveXObject('Msxml2.XMLHTTP');
}
};
varactiveXOld={
createXhrObject:function(){
returnnewActiveXObject('Microsoft.XMLHTTP');
}
};
//Toassignthebranch,tryeachmethod;returnwhateverdoesn'tfail.
vartestObject;
try{
testObject=standard.createXhrObject();
returnstandard;//Returnthisifnoerrorwasthrown.
}
catch(e){
try{
testObject=activeXNew.createXhrObject();
returnactiveXNew;//Returnthisifnoerrorwasthrown.
}
catch(e){
try{
testObject=activeXOld.createXhrObject();
returnactiveXOld;//Returnthisifnoerrorwasthrown.
}
catch(e){
thrownewError('NoXHRobjectfoundinthisenvironment.');
}
}
}
})();