5

JavaScript设计模式及代码实现——单例模式 - 程序员既明

 2 years ago
source link: https://www.cnblogs.com/bidong/p/16635694.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

singleton

1 定义#

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

2 应用时机#

  1. 当一个类的实例被频繁使用,如果重复创建这个实例,会无端消耗资源。比如 dialog 弹窗会被全局重复使用
  2. 业务功能本身决定了全局只能有唯一的实例。比如 redux 管理的数据,只能有唯一的一份

3 应用场景#

  1. 对于前端应用的许多基本组件:比如 dialog、message等等,会被全局频繁使用,就应该维护一个全局唯一的实例,避免重复创建带来不必要的资源消耗。业务组件也同理:比如购物车组件、登录弹窗组件等
  2. 对于一些通用的工具库,经常会使用单例模式。比如我们通常会创建一个全局唯一的 axios 实例来发起网络请求
  3. 对于 redux、vuex 等状态管理库,都采用全局唯一的 store 来存储应用状态数据

4 代码实现#

4.1 全局变量和命名空间#

根据单例模式的定义

// 维护类 A 的唯一实例class A {}window.a = new A(); // 或 global.a = new A(); 浏览器用 window

这种方法存在很明显的缺陷,因为同一项目的所有程序员都可以定义全局的变量 a,很容易造成全局污染。

解决办法是设定一个自己的命名空间来和其他人区分

// 比如我设定自己的命名空间 JiMingwindow.JiMing = { a: new A()}

如果使用 TypeScript ,可以使用关键字 namespace

namespace JiMing { export const a = new A();}

4.2 惰性单例#

上述实现中,我们直接在全局创建了类 A 的单一实例,无论其是否被使用,这在某些场景会造成资源浪费。有时我们希望在用到的时候再创建实例

如下代码利用立即执行函数和闭包来得到 A 的单例获取函数:getSingletonOfA

class A {}const getSingletonOfA = (() => { let instance; return () => { return (instance ??= new A()); };})();

只有在调用getSingletonOfA才会创建 A 的实例,并且会在闭包中将其储存在 instance 中,重复调用getSingletonOfA会获取相同的实例

const a1 = getSingletonOfA();const a2 = getSingletonOfA();console.log(a1 === a2); // true

上述方法能够满足单例模式,但是不够通用,改造如下

const createSingletonUtil = (className) => { let instance; return () => { return (instance ??= new className()); };};

我们封装一个工具函数createSingletonUtil,调用该函数后可以获得任意类的“单例获取函数”

const getSingletonOfA = createSingletonUtil(A);const a3 = getSingletonOfA();const a4 = getSingletonOfA();console.log(a3 === a4); // true

createSingletonUtil 的 TypeScript 实现如下:

class A {}const createSingletonUtil = <T>(className: new () => T) => { let instance: T; return () => { return (instance ??= new className()); };}; const getSingletonOfA = createSingletonUtil<A>(A);const a1 = getSingletonOfA();const a2 = getSingletonOfA();console.log(a1 === a2);

当然惰性单例也有缺点,对于某些类,如果创建实例需要较长时间,这时在用到的时候再创建恐怕来不及,可能会产生其他副作用,比如造成页面卡顿。在此场景下,在应用初始化时就创建其实例或许会有更好的用户体验

上述两种方法根据不同的业务场景择一使用即可

公众号【今天也要写bug】(op-bot)提问答疑


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK