首页 > 其他分享 >[JS] JS单例模式的实现

[JS] JS单例模式的实现

时间:2023-12-16 16:44:06浏览次数:33  
标签:JS 模式 js v1 实例 Video 单例 new ins

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);

控制台输出

image-20231216153428957

方法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);

控制台输出

image-20231216155135470

观察到这种实现下,构造函数只被调用了一次,并且v1v2指向同一个实例。

缺点

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 {}这个匿名类。
  • 而对于v1v2,它们来自于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对于Proxyconstruct更详细的介绍: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();

控制台输出

image-20231216161903412

观察到构造函数只被调用了一次,并且在单例化的新类的原型上添加方法,实例对象v1也可以访问到。

这是因为newVideo是对Video的代理(这里的命名以video.js为准),在newVideo对象上的操作会被应用在Video这个对象上。

至此,完成了JS中较为完善的单例模式实现。

标签:JS,模式,js,v1,实例,Video,单例,new,ins
From: https://www.cnblogs.com/feixianxing/p/javascript-singleton-pattern.html

相关文章

  • tsconfig.json文件配置
    tsconfig.json配置TypeScript使用tsconfig.json文件作为其配置文件,当一个目录中存在tsconfig.json文件,则认为该目录为TypeScript项目的根目录。基础字段files-设置要编译的文件的名称;['./src/main.tsx']include-设置需要进行编译的文件,支持路径模式匹配;['src']......
  • LVS-DR模式
    DR数据包流向分析(1)客户端发送请求到DirectorServer(负载均衡器),请求的数据报文(源IP是CIP,目标IP是VIP)到达内核空间。(2)DirectorServer和RealServer在同一个网络中,数据通过二层数据链路层来传输。(3)内核空间判断数据包的目标IP是本机VIP,此时IPVS(IP虚拟服务器)比对数据......
  • nodejs使用sequelize vscode报错:Type 'Model<any, any, any>' is not a constructor f
    我的模型定义如下:import{Model,DataTypes}from"sequelize";//定义资源模型classRuleextendsModel{}问题:vscdoe报错:Type'Model<any,any,any>'isnotaconstructorfunctiontype.解决:这个问题可能是由于TypeScript类型定义的问题导致的。Model 是Seq......
  • [CSharpTips]C# 控制台程序屏蔽关闭按钮,关闭快速编辑模式,注册关闭事件
    C#控制台程序屏蔽关闭按钮,关闭快速编辑模式,注册关闭事件usingSystem;usingSystem.Collections.Generic;usingSystem.Diagnostics;usingSystem.IO;usingSystem.Linq;usingSystem.Runtime.InteropServices;usingSystem.Text;usingSystem.Threading;usingSystem.......
  • 【前端开发】Next.js VS Nest.js–Nest和Next之间的区别
    web开发框架起到支持作用,并提供了开发应用程序的有效方法。它具有不同的功能和特性,这使得开发过程更容易,开发人员总是能找到最快的编码方式。这就是为什么,无论是后端还是前端框架,大多数开发人员都喜欢使用它。一些框架通常具有非凡的功能,可以最大限度地减少代码,使应用程序开发更快......
  • Java: OpenWeatherMap json Deserialization of Java Objects
    openweathermap.json{"coord":{"lon":114.0683,"lat":22.5455},"weather":[{"id":803,"main":"Clouds","description":"多云",......
  • 设计模式:程序员的秘密武器(高手必备)
    在过去的几十年的职业生涯中,我观察到许多同事在技术方面取得了显著的进步和能力提升。然而,随着时间的推移,他们似乎逐渐遇到了一个发展的瓶颈。尽管大家都渴望以最快的速度提升自己的技能水平,但他们忽视了一个关键的因素,那就是内功修炼。是的,我相信大家都知道我们所说的内功心法指......
  • 代理模式
    用一个代理类将被代理类和别的类分离开,外面的类想访问被代理的类,要走代理类中转。 为什么要控制对对象的访问?一方面,为了节约资源。站在迪米特法则的角度看,如果对于一个非常耗资源的大型对象,如果所有的客户端不管是否使用都去初始化这个对象其实是非常耗资源的。另一方面,为......
  • 设计模式:程序员的秘密武器(高手必备)
    在过去的几十年的职业生涯中,我观察到许多同事在技术方面取得了显著的进步和能力提升。然而,随着时间的推移,他们似乎逐渐遇到了一个发展的瓶颈。尽管大家都渴望以最快的速度提升自己的技能水平,但他们忽视了一个关键的因素,那就是内功修炼。是的,我相信大家都知道我们所说的内功心法指......
  • 观察者模式
    观察者模式通常由两个对象组成:观察者和被观察者。当被观察者状态发生改变时,它会通知所有的观察者对象,使他们能够及时做出响应,所以也被称作“发布-订阅模式”。示例代码/***@authorCreatedbynjyon2023/6/1*报纸接口,即被观察者接口*/publicinterfaceNewspaper{/*......