javascript单例模式
发布于 11 年前 作者 luojianjava 11951 次浏览 最后一次编辑是 8 年前

javascript实现单例模式有很多种方法

一、使用全局变量保存单例

这是最简单的实现方法

function Person(){ 
    this.createTime=new Date(); 
} 

var instance=new Person(); 
function getInstance(){ 
    return instance; 
} 

加载该js时就创建一个Person对象,保存到instance全局变量中,每次使用都取这个对象。如果一次都没使用,那么创建的这个对象则浪费了,我们可以优化一下,

var instance 
function getInstance(){ 
    if(!instance){ 
        instance=new Person(); 
    } 
    return instance; 
} 

这样,第一次使用时才创建对象。 这个方法的缺点是,instance是全局的变量,在多人合作或者开发周期比较长的情况下,很难保证instance不会被其它代码修改或覆盖,很可能到调用的时候,发现instance根本就不是Person对象。 我们考虑下使用闭包来封装起instance,使它不再是全局变量就可以解决这个问题了

二、闭包创建对象

var getInstance(){ 
var instance; 
return function(){ 
        if(!instance){ 
            instance=new Person(); 
        } 
        return instance; 
    } 
}(); 

这样,instance就被封装起来了,不用担心被修改了。 现在通过getInstance()函数可以获得单例了。新的问题,如果我通过new Person()来创建对象,获得的还是多个对象,javascript又不可以像java一样把构造器私有化。那怎么样可以让多次new出来的对象都是一个实例呢?

三、构造函数的静态属性缓存实例

先看代码

function Person(){ 
    //如果已经缓存了实例,则直接返回缓存的实例 
    if(typeof Person.instance==='object'){ 
        return Person.instance; 
    } 
    this.createTime=new Date(); 
    //缓存实例 
    Person.instance=this; 
    return this; 
} 

从代码可以看到,第一次new时,if的条件返回false,会往下走,初始化对象,然后保存对象到Person.instance这个静态属性中。 第二次new 时,if的条件返回true,直接返回Person.instance,不会再往下运行初始化的代码。所以不管new几次,返回的都是第一次创建的对象。

这个方法的缺点和方法一的缺点一样,Person.instance也是公开属性,有可能会被修改。

我们参考方法二,使用闭包来封装一个,也许就能解决该问题了

四、重写构造函数

这个方法要使用闭包,但不能像方法二那么简单,我们需要重写构造函数。

function Person(){ 
    //缓存实例 
    var instance=this; 
    this.createTime=new Date(); 
    //重写构造函数 
    Person=function(){ 
        return instance; 
    } 
} 

第一次new 时,调用原始构造函数先缓存该实例,然后再初始化,同时,重写该构造函数。以后再new 时,永远调用不到原始的构造函数了,只能调用到重写后的构造函数,而这个函数总是返回缓存的instance. 上面的方法似乎没什么问题,但通过下面的测试,可以发现问题

//向原型添加属性 
Person.prototype.prop1=true; 
var p1=new Person(); 
//在创建初始化对象后,再次向该原型添加属性 
Person.prototype.prop2=true; 
var p2=new Person(); 

//开始测试 
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这个属性,并赋值

而在执行 var p1=new Person();之后,Person这个构造函数已经被重写了

所以Person.prototype.prop2=true;是在新的原型下增加prop2这个属性

var p2=new Person(); p2和p1实际上是同一个对象,即原始构造函数创建的对象

所以p1 p2都有prop1这个属性,而没有prop2这个属性

同样的,p1 p2的constructor指向的也是原始的构造函数,而Person此时已不是原来那个函数了

为了能按预期的结果那样运行,可以通过一些修改来实现

function Person(){ 
    //缓存实例 
    var instance=this; 
    //重写构造函数 
    Person=function(){ 
        return instance; 
    } 
    //保留原型属性 
    Person.prototype=this; 
    //实例 
    instance=new Person(); 
    //重置构造函数引用 
    instance.constructor=Person; 

    //其他初始化 
    instance.createTime=new Date(); 
  
    return instance; 
} 

再运行前面的测试代码,结果都是true了。

原文链接:http://blog.csdn.net/bluestarjava/article/details/9967527

7 回复

我觉得没必要为担心别人改到你的instance而做那么多,即使用getInstance()方法来防止,你也一样防止不了别人劫持方法。 所以还不如通过规范命名来解决。 nodejs的话可以直接使用Object.defineProperty(obj, prop, descriptor)

不管怎么说还是学习了 ,不过,为了实现单列这么搞,感觉有点折腾呢,所谓的单列 无非做一个{} 就可以了,都是一个对象,相当于静态类静态方法吧

牛~思考的蛮多的

我一直不懂 为什么js的单例不是 var instance = {} From Noder

@hans-lizihan var instance = {},这种方式没有办法延迟初始化,

@simpliw node项目里面有什么必须延迟初始化的案例么? From Noder

有个很经典的 惰性单例函数 :

var getSingle = function (fn) {
       var result;
	   return function() {
	    	return result || ( result = fn.apply(this, arguments) );
	   }
 }
回到顶部