单例是一种面向对象的软件设计模式,它确保给定的类只实例化一次,并且在许多不同的情况下非常有用,例如创建跨应用程序共享的全局对象和组件。虽然JavaScript支持面向对象编程,但它似乎没有提供许多实现此模式的简单选项。
最灵活的方法(尽管有些先进)涉及使用代理对象。代理对象用于定义所谓的陷阱,即允许为某些操作(如属性查找、赋值等)定义自定义行为的方法。单例模式规定给定类只能有一个实例,这意味着最有用的陷阱是handler.construct(),即new操作符的陷阱。
由于handler程序本身是一个对象,因此我们可以使用它来存储所需类的唯一实例(如果已实例化),同时还可以通过handler.construct()为new操作符提供陷阱。通过这样做,我们可以创建一个对象,该对象可以轻松地重用于我们想要转换为单例的任何类,同时还允许我们为我们可能想要定制的任何其他操作提供额外的陷阱。
以下是函数的最基本版本,它接受一个类并将其转换为单例,基于上述解释:
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;
}
});
}
下面是一个简单的实际例子,可以更好地理解它的作用:
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'
在上面的示例中,您可以看到,第二次实例化MySingleToClass时,由于实例已经存在,所以没有发生任何事情,因此返回实例,而不是创建新对象。虽然这是singletonify函数的最小实现,但可以轻松地对其进行扩展,以进一步修改行为,甚至在后续调用中使用传递给构造函数的一些数据来更新其持有的实例。