华夏吉祥 发表于 2023-12-17 02:50:53

[JS] JS单例模式的实现

JS 单例模式的实现

单例模式简介

单例模式(Singleton Pattern)是最简单的设计模式之一。这种类型的设计模式属于创建型模式,提供了一种创建对象的最佳方式。
特点:

[*]意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
[*]主要解决:一个全局使用的类频繁地创建与销毁。
[*]何时使用:当您想控制实例数目,节省系统资源的时候。
[*]如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
首先对比一下平时不使用单例模式的情况:
在不适用单例模式的情况下,如下,会得到不同的多个实例:
video.js
class Video{
    constructor() {
      console.log("video created");
    }
}

export { Video };main.js
import { Video } from "./video.js";

const v1 = new Video();
const v2 = new Video();

console.log(v1 === v2);控制台输出

方法1:提前构造实例

提前构造一个实例,只向外部暴露该实例的引用,从而实现单例。
但是缺点是需要提前构造实例,而无法做到在需要的时候创建实例。
class Video{
    constructor() {
      console.log("video created");
    }
}

const v = new Video();

export { v };方法2:构造方法私有化

video.js
class Video{
    private constructor() {
      console.log("video created");
    }

    static _ins = null;
    static getInstance(){
      if(!this._ins){
            this._ins = new Video();
      }
      return this._ins;
    }
}

export { Video };main.js
import { Video } from "./video.js";

const v1 = Video.getInstance();
const v2 = Video.getInstance();

console.log(v1 === v2);通过将构造方法私有化,外部无法通过new实例化对象,只能通过静态方法getInstance获取实例。
而在类的内部实现中,使用_ins确保只存在一个实例。
缺点:
原生JS不存在private,需要使用TS。
在JS中,如果剔除private,仅通过getInstance也可以实现单例模式。但是这种方法不严格,无法确保每一个调用者不会使用构造函数创建新的实例。
方法3:通用方法——将任意类转为单例

singleton.js
export function singleton(className){
    let ins;
    return class{
      constructor(...args) {
            if(!ins){
                ins = new className(...args);
            }
            return ins;
      }
    };
}

[*]这个函数接收一个类,进行改造之后返回一个新的类;
[*]使用闭包,存储实例对象ins;
[*]新的类的构造函数相当于拦截作用:

[*]如果ins不存在,则将传入的参数转交给原来的类的构造函数,并创建一个实例;
[*]如果ins存在,则直接返回存储在闭包中的实例对象。

video.js
import { singleton } from "./singleton.js";

class Video{
    constructor() {
      console.log("video created");
    }
}

const newVideo = singleton(Video);

export { newVideo as Video };main.js
import { Video } from "./video.js";

const v1 = new Video();
const v2 = new Video();

console.log(v1 === v2);控制台输出

观察到这种实现下,构造函数只被调用了一次,并且v1和v2指向同一个实例。
缺点:
main.js
Video.prototype.play = function(){
    console.log("play");
}

v1.play(); // Uncaught TypeError: v1.play is not a function在这个案例中,我们试图在Video的原型上添加一个方法,并通过实例对象v1调用,但是v1所处的原型链上并不能找到这个方法。
再回过头来观察singleton.js的实现:
export function singleton(className){
    let ins;
    return class{
      constructor(...args) {
            if(!ins){
                ins = new className(...args);
            }
            return ins;
      }
    };
}

[*]在main.js中,我们使用的Video类来自于video.js的导出,实际上已经是经过singleton函数改造的类,也就是上面这段代码中,return class {}这个匿名类。
[*]而对于v1或v2,它们来自于ins这个实例对象,它由上面这段代码的new className()创建,也就是说它来自于最“简单”的、没有经过单例化的那个Video类。
[*]综上,v1并不是由那个匿名类创建的,所以它们不在同一原型链上。这也是这种单例模式实现方式的缺点,需要改进。
方法4:使用代理

这个方法是对方法3的改进,使用Proxy API对类进行代理,往新的类的原型上添加方法,也会被添加到原来的类的原型上,由此解决了方法3的缺点。
singleton.js
export function singleton(className){
    let ins;
    return new Proxy(className, {
      construct(target, ...args){
            if(!ins){
                ins = new target(...args);
            }
            return ins;
      }
    });
}MDN对于Proxy中construct更详细的介绍:handler.construct() - JavaScript | MDN (mozilla.org)
这里的constuct主要是拦截外部的new操作,函数参数target指向代理对象,也就是这里的className,即需要被单例化的类。
其它逻辑和方法3一致,使用闭包,通过ins存储实例对象。
video.js
import { singleton } from "./singleton.js";

class Video{
    constructor() {
      console.log("video created");
    }
}

const newVideo = singleton(Video);

export { newVideo as Video };main.js
import { Video } from "./video.js";

const v1 = new Video();
const v2 = new Video();

console.log(v1 === v2);Video.prototype.play = function(){    console.log("play");}v1.play();控制台输出

观察到构造函数只被调用了一次,并且在单例化的新类的原型上添加方法,实例对象v1也可以访问到。
这是因为newVideo是对Video的代理(这里的命名以video.js为准),在newVideo对象上的操作会被应用在Video这个对象上。
至此,完成了JS中较为完善的单例模式实现。

来源:https://www.cnblogs.com/feixianxing/p/javascript-singleton-pattern.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: [JS] JS单例模式的实现