likes
comments
collection
share

前端设计模式之单例模式(四)

作者站长头像
站长
· 阅读数 15
  • 单例模式,即对一个 class 只能创建一个实例,即便调用多次
  • 如一个系统的登录框、遮罩层,可能会被很多地方调用,但登录框只初始化一次即可,后续直接复用
  • Vuex Redux 这些全局数据存储,eventBus 全局数据通信等,全局只有一个实例

伪代码理解

登录框,初始化多次没必要

class LoginModal { }

// modal1 和 modal2 功能一样,没必要初始化两次
const modal1 = new LoginModal()
const modal2 = new LoginModal()
```

全局存储,初始化多个实例,会出错。

```js
class Store { /* get set ... */ }

const store1 = new Store()
store1.set(key, value)

const store2 = new Store()
store2.get(key) // 获取不到

使用TS特性

前端设计模式之单例模式(四)

  • static 静态属性和方法
  • private 外部无法直接初始化
class Singleton {
  // private - 外部无法初始化
  private constructor() {}

  // static 属性
  private static instance: Singleton | null;

  // static 方法
  static getInstance(): Singleton {
    // 这里也可以写 `this.instance` ,注意和实例方法中 this 的区别!!!
    if (Singleton.instance == null) {
      Singleton.instance = new Singleton();
    }
    return Singleton.instance;
  }
}

// const s1 = new Singleton() // 直接初始化会报错
// Singleton.instance // 直接访问 instance 也会报错

// 创建实例
const s1 = Singleton.getInstance();
const s2 = Singleton.getInstance();

console.log(s1 === s2); // true

不使用 TS 特性

最常见的方式,使用闭包

function genGetInstance() {
  let instance; // 闭包

  class Singleton {}

  return () => {
    if (instance == null) {
      instance = new Singleton();
    }
    return instance;
  };
}

const getInstance = genGetInstance();

const s1 = getInstance();
const s2 = getInstance();
console.log(s1 === s2); // true

结合模块化语法,会更好一些

let instance; // 闭包

class Singleton {}

// 外部只能 import 这个函数
export default () => {
  if (instance == null) {
    instance = new Singleton();
  }
  return instance;
};

是否符合设计原则?

  • 内部封装 getInstance ,内聚,解耦,符合开放封闭原则,对扩展开放,对修改封闭

注意:JS 是单线程语言,如果是 Java 等多线程语言,单例模式需要加线程锁



登录框场景

一个页面有很多地方调用登录框,使用单例模式

class LoginForm {
  private state: string = "hide"; // 'hide' / 'show'

  private constructor() {}

  show() {
    if (this.state === "show") {
      console.log("已经显示了");
      return;
    }
    console.log("显示 LoginForm");
    this.state = "show";
  }

  hide() {
    if (this.state === "hide") {
      console.log("已经隐藏了");
      return;
    }
    console.log("隐藏 LoginForm");
    this.state = "hide";
  }

  private static instance: LoginForm | null = null;
  static getInstance(): LoginForm {
    // 注意这里的 this
    if (this.instance == null) this.instance = new LoginForm();
    return this.instance;
  }
}

const loginForm1 = LoginForm.getInstance();
const loginForm2 = LoginForm.getInstance();
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>单例模式弹框</title>
  </head>
  <style>
    #modal {
      height: 200px;
      width: 200px;
      line-height: 200px;
      position: fixed;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
      border: 1px solid black;
      text-align: center;
    }
  </style>
  <body>
    <button id="open">打开弹框</button>
    <button id="close">关闭弹框</button>
  </body>
  <script>
    // 核心逻辑,这里采用了闭包思路来实现单例模式
    const Modal = (function () {
      let modal = null;
      return function () {
        if (!modal) {
          modal = document.createElement("div");
          modal.innerHTML = "我是一个全局唯一的Modal";
          modal.id = "modal";
          modal.style.display = "none";
          document.body.appendChild(modal);
        }
        return modal;
      };
    })();

    // 点击打开按钮展示模态框
    document.getElementById("open").addEventListener("click", function () {
      // 未点击则不创建modal实例,避免不必要的内存占用;此处不用 new Modal 的形式调用也可以
      const modal = new Modal();
      modal.style.display = "block";
    });

    // 点击关闭按钮隐藏模态框
    document.getElementById("close").addEventListener("click", function () {
      const modal = new Modal();
      if (modal) {
        modal.style.display = "none";
      }
    });
  </script>
</html>