翼度科技»论坛 编程开发 JavaScript 查看内容

[JS] JS单例模式的实现

6

主题

6

帖子

18

积分

新手上路

Rank: 1

积分
18
JS 单例模式的实现

单例模式简介

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

  • 意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
  • 主要解决:一个全局使用的类频繁地创建与销毁。
  • 何时使用:当您想控制实例数目,节省系统资源的时候。
  • 如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
首先对比一下平时不使用单例模式的情况:
在不适用单例模式的情况下,如下,会得到不同的多个实例:
video.js
  1. class Video{
  2.     constructor() {
  3.         console.log("video created");
  4.     }
  5. }
  6. export { Video };
复制代码
main.js
  1. import { Video } from "./video.js";
  2. const v1 = new Video();
  3. const v2 = new Video();
  4. console.log(v1 === v2);
复制代码
控制台输出

方法1:提前构造实例

提前构造一个实例,只向外部暴露该实例的引用,从而实现单例。
但是缺点是需要提前构造实例,而无法做到在需要的时候创建实例。
  1. class Video{
  2.     constructor() {
  3.         console.log("video created");
  4.     }
  5. }
  6. const v = new Video();
  7. export { v };
复制代码
方法2:构造方法私有化

video.js
  1. class Video{
  2.     private constructor() {
  3.         console.log("video created");
  4.     }
  5.     static _ins = null;
  6.     static getInstance(){
  7.         if(!this._ins){
  8.             this._ins = new Video();
  9.         }
  10.         return this._ins;
  11.     }
  12. }
  13. export { Video };
复制代码
main.js
  1. import { Video } from "./video.js";
  2. const v1 = Video.getInstance();
  3. const v2 = Video.getInstance();
  4. console.log(v1 === v2);
复制代码
通过将构造方法私有化,外部无法通过new实例化对象,只能通过静态方法getInstance获取实例。
而在类的内部实现中,使用_ins确保只存在一个实例。
缺点
原生JS不存在private,需要使用TS。
在JS中,如果剔除private,仅通过getInstance也可以实现单例模式。但是这种方法不严格,无法确保每一个调用者不会使用构造函数创建新的实例。
方法3:通用方法——将任意类转为单例

singleton.js
  1. export function singleton(className){
  2.     let ins;
  3.     return class{
  4.         constructor(...args) {
  5.             if(!ins){
  6.                 ins = new className(...args);
  7.             }
  8.             return ins;
  9.         }
  10.     };
  11. }
复制代码

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

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

video.js
  1. import { singleton } from "./singleton.js";
  2. class Video{
  3.     constructor() {
  4.         console.log("video created");
  5.     }
  6. }
  7. const newVideo = singleton(Video);
  8. export { newVideo as Video };
复制代码
main.js
  1. import { Video } from "./video.js";
  2. const v1 = new Video();
  3. const v2 = new Video();
  4. console.log(v1 === v2);
复制代码
控制台输出

观察到这种实现下,构造函数只被调用了一次,并且v1和v2指向同一个实例。
缺点
main.js
  1. Video.prototype.play = function(){
  2.     console.log("play");
  3. }
  4. v1.play(); // Uncaught TypeError: v1.play is not a function
复制代码
在这个案例中,我们试图在Video的原型上添加一个方法,并通过实例对象v1调用,但是v1所处的原型链上并不能找到这个方法。
再回过头来观察singleton.js的实现:
  1. export function singleton(className){
  2.     let ins;
  3.     return class{
  4.         constructor(...args) {
  5.             if(!ins){
  6.                 ins = new className(...args);
  7.             }
  8.             return ins;
  9.         }
  10.     };
  11. }
复制代码

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

这个方法是对方法3的改进,使用Proxy API对类进行代理,往新的类的原型上添加方法,也会被添加到原来的类的原型上,由此解决了方法3的缺点。
singleton.js
  1. export function singleton(className){
  2.     let ins;
  3.     return new Proxy(className, {
  4.         construct(target, ...args){
  5.             if(!ins){
  6.                 ins = new target(...args);
  7.             }
  8.             return ins;
  9.         }
  10.     });
  11. }
复制代码
MDN对于Proxy中construct更详细的介绍:handler.construct() - JavaScript | MDN (mozilla.org)
这里的constuct主要是拦截外部的new操作,函数参数target指向代理对象,也就是这里的className,即需要被单例化的类。
其它逻辑和方法3一致,使用闭包,通过ins存储实例对象。
video.js
  1. import { singleton } from "./singleton.js";
  2. class Video{
  3.     constructor() {
  4.         console.log("video created");
  5.     }
  6. }
  7. const newVideo = singleton(Video);
  8. export { newVideo as Video };
复制代码
main.js
  1. import { Video } from "./video.js";
  2. const v1 = new Video();
  3. const v2 = new Video();
  4. 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】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具