LWDW!

Learn the work from doing the work🍺

js设计模式——单例模式
Posted on 2016-11-28

单例模式应该是我最早接触的设计模式之一,尽管如此,前两天在写IFE的任务时还是懵比了好一阵,他喵的还是总结一下比较好。

PS:以下代码及相当一部分内容来源于 《javascript设计模式与开发实践》——曾探 ,非完全原创!

什么是单例模式

“保证一个类有且仅有一个实例,并提供一个访问他的全局访问点”。

标准的单例模式

原理很简单,就是用一个变量来标志当前是否为某个类创建过对象,如果创建过,下一次获取该类的实例时,直接返回该对象:

var Singleton = function(name) {
    this.name = name;
    this.instance = null;
};
Singleton.prototype.getName = function() {
    alert(this.name);
};
Singleton.getInstance = function(name) {
    if (!this.instance) {
        this.instance = new Singleton(name);
    }
    return this.instance;
};
var a = Singleton.getInstance('sven1');
var b = Singleton.getInstance('sven2');
alert(a === b);
// true

或者:

var Singleton = function(name) {
    this.name = name;
};
Singleton.prototype.getName = function() {
    alert(this.name);
};
Singleton.getInstance = (function() {
    var instance = null;
    return function(name) {
        if (!instance) {
            instance = new Singleton(name);
        }
        return instance;
    }
})();

这两种方法都是通过getInstance方法来获取唯一对象,不过缺点很明显,如果这个类使用者使用new方法创建实例,还是会有新的实例产生。

JS的单例模式

JS其实本身没有提供类的实现方法,当然在ES6中我们可以使用CLASS了,不过这只是语法糖而已,并非是新的继承模型,那我们其实可以搞一些JS特色的单例模式啊。

全局变量?

全局变量当然不是单例模式,但是我们其实常常这么用,比如var a = {};,这样创建对象自然是独一无二的,不过全局变量容易造成命名空间污染,对此我们可以使用命名空间:

var namespace1 = {
    a: function() {
        alert(1);
    },
    b: function() {
        alert(2);
    }
};

或使用闭包:

var user = (function() {
    var __name = 'sven',
        __age = 29;
    return {
        getUserInfo: function() {
            return __name + '-' + __age;
        }
    }
})();

惰性单例

惰性单例就是指在需要时才创建对象实例,上文的代码其实就是惰性单例了,但是实在是不够抽象,我们可以分离出真正interesting的部分:

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

注意是创建对象的方法被当成参数传入getSingle函数,在让getSingle返回一个新的**函数!**并且用result返回fn的计算结果,因为result在闭包中,所以不会被销毁,一个完整的例子:

var createLoginLayer = function() {
    var div = document.createElement('div');
    div.innerHTML = '我是登录浮窗';
    div.style.display = 'none';
    document.body.appendChild(div);
    return div;
};
var createSingleLoginLayer = getSingle(createLoginLayer);
document.getElementById('loginBtn').onclick = function() {
    var loginLayer = createSingleLoginLayer();
    loginLayer.style.display = 'block';
};

最疼的方法——重写构造函数

这个方法是在这篇博客看到的,给人一种《逃生2》的断子绝孙之感:

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

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

    return instance;
}

简单的来说就是构造函数第一次调用时,一开始就直接改写自身的构造函数令其将来执行时直接返回实例,再有条不紊地保留原型属性构造函数引用(否则一旦对象被创建,其原型将被改变),最后再开始构造实例的工作,实在太优雅(而且感觉很疼)了[茶]。


诶~设计模式真是四国一啊ᕕ ( ᐛ ) ᕗ