在 JavaScript 中实现单例

单例是一种面向对象的软件设计模式,它确保给定的类只被实例化一次。
比如,数据库连接。

Proxy

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

#. 语法

1
2

new Proxy(target, handler)

#. 术语

  • handler 包含捕捉器(trap)的占位符对象(处理器对象)。
  • traps 允许为某些操作(例如属性查找、赋值等)定义自定义行为的方法。这类似于操作系统中捕获器的概念。
  • target 被 Proxy 代理虚拟化的对象。它常被作为代理的存储后端。根据目标验证关于对象不可扩展性或不可配置属性的不变量(保持不变的语义)。

#. Proxy 有2个参数

  • target 被代理的对象。它可以是任何类型的对象,包括内置的数组,函数甚至是另一个代理对象。
  • handler 被代理对象上的自定义行为。它是一个对象,它的属性提供了部分操作时所发生的处理函数。

使用 Proxy 构建单例模式

  • 单例模式规定给定的类只能有一个实例。
  • 这意味着最有用的捕获器(注意:proxy中有多种捕获器)是 handler.construct()
  • handler.construct() 方法用于拦截 new 操作符.
    为了使new操作符在生成的Proxy对象上生效,用于初始化代理的目标对象自身必须具有[[Construct]]内部方法(即 new target 必须是有效的)。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11

const singletonify = (className) => {
  return new Proxy(className.prototype.constructor, {
    instance: null,
    construct: (target, argumentsList) => {
      if (!this.instance)
        this.instance = new target(...argumentsList);
      return this.instance;
    }
  });
}

使用示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17

class MyClass {
  constructor(msg) {
    this.msg = msg;
  }

  printMsg() {
    console.log(this.msg);
  }
}

MySingletonClass = singletonify(MyClass);

const myObj = new MySingletonClass('first');
myObj.printMsg();           // 'first'
const myObj2 = new MySingletonClass('second');
myObj2.printMsg();          // 'first'

可以看到 第二次 MySingletonClass 被实例化了,但什么都没有发生。

这是因为一个实例已经存在,所以它被 返回 而不是创建一个新对象。