**简而言之,“应用程序中贯穿始终的单一全局实例就是单例的意思。”
在整个应用程序中都可以访问的、只能创建一次的类称为单例。应用程序的不同部分都可以使用这个单一实例,这使得单例在管理应用程序中的全局状态时非常有用。
首先,让我们看一下使用 ES2015 类可以创建一个什么样的单例。对于这个例子,我们将构建一个计数器
类,它有:
getInstance
方法,返回实例的值getCount
方法,返回counter
变量的当前值increment
方法,使counter
的值加1decrement
方法,使counter
的值减1let counter = 0;
class Counter {
getInstance() {
return this;
}
getCount() {
return counter;
}
increment() {
return ++counter;
}
decrement() {
return --counter;
}
}
但是,这个类还不符合单例的标准!单例应该只能实例化一次。目前,我们可以创建多个计数器
类的实例。
计数器
类的实例。一种确保只能创建一个实例的方法是创建一个名为instance
的变量。在计数器
的构造函数中,当创建新实例时,我们可以将instance
设置为对该实例的引用。如果instance
变量已经有值,我们可以通过检查来防止新实例化。如果是这种情况,则实例已经存在。这不应该发生:应该抛出错误以通知用户。
let instance;
let counter = 0;
class Counter {
constructor() {
if (instance) {
throw new Error("You can only create one instance!");
}
instance = this;
}
getInstance() {
return this;
}
getCount() {
return counter;
}
increment() {
return ++counter;
}
decrement() {
return --counter;
}
}
const counter1 = new Counter();
const counter2 = new Counter();
// Error: You can only create one instance!
完美!我们不能再创建多个实例了。
让我们从counter.js
文件导出计数器
实例。但在此之前,我们也应该冻结该实例。 Object.freeze
方法确保使用代码无法修改单例。冻结实例上的属性不能添加或修改,这减少了意外覆盖单例上值的风险。
const singletonCounter = Object.freeze(new Counter());
export default singletonCounter;
让我们看一个实现了计数器
示例的应用程序。我们有以下文件:
counter.js
:包含计数器
类,并将计数器
实例导出为默认导出index.js
:加载 redButton.js
和 blueButton.js
模块redButton.js
:导入计数器
,并将计数器
的increment
方法作为红色按钮的事件侦听器添加,通过调用getCount
方法记录counter
的当前值blueButton.js
:导入计数器
,并将计数器
的increment
方法作为蓝色按钮的事件侦听器添加,通过调用getCount
方法记录counter
的当前值// index.js 👇
import "./redButton";
import "./blueButton";
console.log("Click on either of the buttons 🚀!");
// counter.js 👇
let instance;
let counter = 0;
class Counter {
constructor() {
if (instance) {
throw new Error("You can only create one instance!");
}
instance = this;
}
getInstance() {
return this;
}
getCount() {
return counter;
}
increment() {
return ++counter;
}
decrement() {
return --counter;
}
}
const singletonCounter = Object.freeze(new Counter());
export default singletonCounter;
// redButton.js 👇
import Counter from "./counter";
const button = document.getElementById("red");
button.addEventListener("click", () => {
Counter.increment();
console.log("Counter total: ", Counter.getCount());
});
// blueButton.js 👇
import Counter from "./counter";
const button = document.getElementById("blue");
button.addEventListener("click", () => {
Counter.increment();
console.log("Counter total: ", Counter.getCount());
});
blueButton.js
和redButton.js
从counter.js
中导入相同的实例。这个实例在两个文件中都导入为计数器
。
当我们在redButton.js
或blueButton.js
中调用increment
方法时,计数器
实例上的counter
属性的值会在两个文件中更新。点击红色按钮还是蓝色按钮都没有关系:相同的值在所有实例之间共享。这就是为什么计数器的值会继续加1的原因,即使我们是在不同的文件中调用该方法。
将实例化限制为只有一个实例可以潜在地节省很多内存空间。我们不必为每次新建实例设置内存,只需要为那个在整个应用程序中都引用的实例设置内存。然而,单例实际上被认为是一种反面模式*,在 JavaScript 中可以(或… 应该)避免使用它*。
let count = 0;
const counter = {
increment() {
return ++count;
},
decrement() {
return --count;
}
};
Object.freeze(counter);
export { counter };