js设计模式之单例模式

理想单例模式

  • 对象只需要实例化一次
  • 惰性实例化
  • 禁止修改属性(可选)...因为共享一个示例 如果擅自修改对象属性 影响比较大

方式1:实例挂载在构造函数上

const $ = function() {
    this.name = 'xiao lang'
    if ($.instance) {
        return $.instance;
    } else {
        $.instance = this;
    }
};

$.prototype.a = 1;

// test
const a = new $();
const b = new $();
console.log(a === b, a.a, b.a); // true , 1, 1

`为防止有人修改$.instance 可使用symbol`

let $;
(() => {
    const sym = Symbol('instance');
    $ = function() {
        this.name = 'xiao lang';
        if ($[sym]) {
            return $[sym];
        } else {
            $[sym] = this;
        }
    };
})();
//test
const a = new $();
const b = new $();
console.log(a === b); //true

`希望不要修改自有属性 可使用proxy`

let $;
(() => {
    let sym = Symbol('instance');
    $ = function() {
        this.name = 'xiao lang';
        if ($[sym]) {
            return $[sym];
        } else {
            $[sym] = this;
            $[sym] = new Proxy($[sym], {
                set() {
                    throw new Error('can not rewrite it');
                }
            });
            return $[sym];  //注意此处不能默认让构造函数return this 了  因为$[sym]加了proxy  this并没有加
        }
    };
})();

const a = new $();
const b = new $();
console.log(a === b); //true
$.prototype.a = 1
console.log(a.a)    //1
a.test = '666'  // error:can not rewrite it

方式2:闭包 & 重写构造函数


let $ = function() {
    this.name = 'xiao lang';
    let instance = this;
    $ = function() {
        return instance;
    };
};
const a = new $();
const b = new $();
// test
console.log(a === b) // true
// 但是原型
$.prototype.test = 1
console.log(a.test) // undefined  因为原来的$已经被重写 而instance还是原来的实例

`尝试解决上面这个问题`

let $ = function() {
    this.name = 'xiao lang';
    let instance = this;
    const temp = $.prototype
    $ = function() {
        return instance;
    };
    $.prototype = temp
    instance.constructor = $;
};
// test
const a = new $();
const b = new $();
$.prototype.c = 1;
console.log(a === b, a.c, a instanceof $, a.constructor === $); //true 1 true true

`加上proxy禁止改动`

let $ = function() {
    this.name = 'hongfa';
    let instance = this;
    instance = new Proxy(instance, {
        set(target, key, value, proxy) {
            if (key === 'constructor') return Reflect.set(target, key, value, proxy);
            throw new Error('can not rewrite it');
        }
    });
    const temp = $.prototype
    $ = function() {
        return instance;
    };
    $.prototype = temp
    instance.constructor = $;
    return instance;  // 注意手动return
};

const a = new $();
const b = new $();
$.prototype.c = 1;
console.log(a === b, a.c, a instanceof $, a.constructor === $); //true 1 true true

a.name = 'test' // error

`不用proxy实现属性只读`

let $ = function() {
    const prop = {
        name:'xiaolang'
    }
    this.getProp=(key)=>{
        return prop[key]
    }
    let instance = this;
    const temp = $.prototype
    $ = function() {
        return instance;
    };
    $.prototype = temp
    instance.constructor = $;
};

const a = new $()
const b = new $()

console.log(a===b,a.getProp('name')) // true xiaolang

ts实现


`ts实现私有属性就简单多了`

class Single {
    private static instance: any;
    readonly name: string = 'xiaolang'
    constructor() {
        if (Single.instance) {
            return Single.instance;
        } else {
            Single.instance = this;
        }
    }
}

const a = new Single();
const b = new Single();

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

`扩展属性 使用继承的方式`

class newSingle extends Single {
    x: string = 'test'
}

const a = new A();
const b = new A();

console.log(a === b, a.x); //true,test

reference

  • https://www.cnblogs.com/TomXu/archive/2012/02/20/2352817.html
  • https://github.com/shichuan/javascript-patterns/blob/master/design-patterns/singleton.html