JS 之 单例模式

171 阅读2分钟

所谓单例模式,指某个类永远只会有一个实例。

单例模式所解决的问题是:当类实例可以共用的时候,防止频繁地创建和销毁类实例,节约资源。

单例模式的实现思路很简单:判断该类当前是否已经有实例,如果有则返回,如果没有则创建。

基于这个思路,提供两种实现方案。

1、基于类的方式实现

参考传统面向对象语言的实现方式,可以创建单例模式的类对象如下所示:

class Singleton {
    constructor(name) {
        this.name = name;
        this.instance = null;
    }
    // 该接口用于对该类进行实例化
    static getInstance(name) {
        if(!this.instance) {
            this.instance = new Singleton(name);
        }
        return this.instance;
    }
}

功能测试验证如下:

let a = Singleton.getInstance('Ayaka');
let b = Singleton.getInstance('princess');

console.log(a === b); // true

2、基于闭包的方式实现

与传统面向对象语言不同,JavaScript 的闭包能力,使得在实现单例模式上能够更加便捷。

const createSingleInstance = (function () {
    let instance = null;
    return function(name) {
        if (!instance) {
            instance = {name};
        }
        return instance;
    }
})();

// 测试
let a = createSingleInstance('Ayaka');
let b = createSingleInstance('princess');

console.log(a === b); // true

👆 这里我们通过一个匿名立即执行函数,创建了个闭包,将单例封装了起来。

这样做不够好的地方在于,如果我需要创建另一种单例对象,就得将这份代码拷贝下来,再修改里面的逻辑,如下所示。

const createOtherSingleInstance = (function () {
    let instance = null;
    return function(width, height) {
        if (!instance) {
            instance = {width, height};
        }
        return instance;
    }
})();

优化一下,我们可以把管理单例的闭包逻辑提取成一个单独的函数进行处理,将创建对象和管理单例的职责被分布在两个不同的方法中。 👇

const getSingle = function(fn) {
    let result;
    return function(...args) {
        return result || (result = fn.apply(this, args));
    }
}

const createSingleInstance = getSingle(function(name) {
    return {name};
})

const createOtherSingleInstance = getSingle(function(width, height) {
    return {width, height}
})

// 测试
let a = createSingleInstance('Ayaka');
let b = createSingleInstance('princess');
console.log(a === b); // true

let c = createOtherSingleInstance(5, 10);
let d = createOtherSingleInstance(6, 11);
console.log(c === d); // true

最后再看看来自《JavaScript设计模式与开发实践》的相关实践代码 👇。

var getSingle = function(fn) {
    var result;
    return function() {
        return result || (result = fn.apply(this, arguments));
    }
}
// 使用一
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';
};
// 使用二
const createSingleIframe = getSingle(function() {
    const iframe = document.createElement('iframe');
    document.body.appendChild(iframe);
    return iframe;
});
document.getElementById('loginBtn').onclick = function() {
    var loginLayer = createSingleIframe();
    loginLayer.src = 'http://baidu.com'
}

参考

《JavaScript设计模式与开发实践》 第4章 单例模式