Shane Jix

TypeScript 中的单例模式

create:October 24, 2021  update:April 12, 2022  ☕️ 4 min read

同步链接: https://www.shanejix.com/posts/TypeScript 中的单例模式/

单例模式(单体):一个类有且只实例化一个实例对象

更具体的说:

如果一个类对外只提供一个对象实例,并且对外提供一个唯一可以访问该对象的方法或者属性,

那么这样就可以保证该对象的唯一性

那为啥不直接声明一个对象而是通过类实例化出一个对象?

显然是最大限度的利用面向对象的思想:更具封装性,更易于扩展

场景

何时使用单例模式呢?当然是从单例模式本身的重要特征考虑:唯一性

唯一:独立无二,有且仅有一个。符合此特征就可以考虑使用单例模式。

例如:

场景一:客户端存储

localStore/sessionStory => 同源策略,同协议,同域名,同端口共用同一份数据 => 单例模式

场景二:全局状态管理

全局状态 store => SPA 共用全局属性和方法(唯一的实例对象) => 单例模式 (react-redux , vuex 等)

当然,还有很多场景~譬如模拟一个地球,模拟太阳系什么的

模式

在 typescript 中如何实现单例模式呢?就以场景一中的客户端存储为例:

第一步:私有化构造器

class LocalStorageLayz {
  // 私有化构造器
  private constructor() {}
}

第二步:对外提供可访问的方法,调用该方法可以得到当前类的唯一对象实例

class LocalStorageLayz {
  // 静态属性 引用 LocalStotrage 类的唯一实例对象
  static localStorage: LocalStorageLayz;

  // 私有化构造器
  private constructor() {}

  // 提供一个外部可访问的的静态方法
  public static getInstance() {
    if (!this.localStorage) {
      this.localStorage = new LocalStorageLayz();
    }

    return this.localStorage;
  }

  // 实例方法
  public getItem(key: string) {
    let val = localStorage.getItem(key);
    return val !== null ? JSON.parse(val) : null;
  }

  // 实例方法
  public setItem(key: string, val: any) {
    localStorage.setItem(key, JSON.stringify(val));
  }
}

第三步:外部调用第二步对外提供的方法,得到唯一实例

const instanceLayz = LocalStorageLayz.getInstance();

instanceLayz.setItem("instanceLayz", { "1": 1, "2": 3 });

let val = instanceLayz.getItem("instanceLayz");

console.log("inatanceLayz", val);

其中第二步中,可以通过对外提供的静态属性直接引用实例对象,也可以通过对外提供的静态方法去初始化静态属性。如下:

class LocalStorage {
  // 静态属性 引用 LocalStotrage 类的唯一实例对象
  static localStorage: LocalStorage = new LocalStorage();

  // 私有化构造器
  private constructor() {}

  // 实例方法
  public getItem(key: string) {
    let val = localStorage.getItem(key);
    return val !== null ? JSON.parse(val) : null;
  }

  // 实例方法
  public setItem(key: string, val: any) {
    localStorage.setItem(key, JSON.stringify(val));
  }
}

区别在于,通过静态方法可以手动控制挂载唯一实例的时机会,而通过静态属性则会在类加载时挂载到静态属性上

const instance = LocalStorage.localStorage;
instance.setItem("instance", { "3": 3, "4": 4 });
const value = instance.getItem("instance");
console.log("instance", value);

扩展

JavaScript 实现

function Singleton() {}

Singleton.getInstance = function (...args) {
  if (!Singleton.instance) {
    Singleton.instance = new Singleton();
  }

  return Singleton.instance;
};

Singleton.instance = null;

const a = Singleton.getInstance();
const b = Singleton.getInstance();

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

闭包实现

const Singleton = (function () {
  let instance = null;

  return function () {
    if (instance) {
      return instance;
    }

    return (instance = this);
  };
})();

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

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

总结

简单实现了通过 typescript 中的静态成员(静态方法/静态属性)实现 单例模式的具体逻辑:首先明确单例模式的的重要特征:单个实例;然后如何通过保证一个类有且只有一个实例呢?

  • 对外提供一个静态属性或者静态方法;
  • 在类挂载的时候实例化单例或者通过提供的静态方法手动实例化单例

也许你会疑惑为啥单例为啥不直接放到原型上去?而是放在类的静态成员上呢?这里前提是得知道类的静态成员以及 JavaScript 中的原型机制!

references

作者:shanejix 出处:https://www.shanejix.com/posts/TypeScript 中的单例模式/ 版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。 声明:转载请注明出处!

Edit on GitHubDiscuss on GitHub


Shane Jix

Personal blog by Shane Jix. I explain with words and code.

LinksTools
© 2019 - 2022, Built withGatsby