JavaScript设计模式:单例模式
单例模式(Singleton Pattern) 是一种对象创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取该实例。
核心思想
- 唯一性:保证一个类只有一个实例。
- 全局访问点:提供一种方式来获取这个唯一的实例。
通常用于管理共享资源,如配置信息、线程池、缓存等。
模式结构
- Singleton(单例类):包含一个私有静态变量来持有类的唯一实例,并提供一个公有静态方法来获取这个实例。
代码实现
类实现方式
class Singleton {
static instance = null;
constructor() {
// 私有构造函数,防止外部实例化
}
static getInstance() {
if (!Singleton.instance) {
// 确保只创建一个实例
Singleton.instance = new Singleton();
}
return Singleton.instance;
}
doSomething() {
console.log("Doing something...");
}
}
// 获取单例对象并调用方法
const singleton = Singleton.getInstance();
singleton.doSomething();
// 再次获取单例对象,将得到同一个实例
const anotherInstance = Singleton.getInstance();
console.log(singleton === anotherInstance); // 输出 true
- 通过
Singleton.getInstance()
方法,无论何时调用,都会返回同一个实例。 - 单例对象的创建是延迟的,即在第一次调用
getInstance()
方法时才创建。
闭包实现方式
const Singleton = (function () {
let instance;
function init() {
// 私有变量和方法
const privateMethod = function () {
console.log("Private method");
};
return {
publicMethod: function () {
console.log("Public method");
privateMethod();
},
};
}
return {
getInstance: function () {
if (!instance) {
instance = init();
}
return instance;
},
};
})();
// 获取单例对象并调用方法
const singleton = Singleton.getInstance();
singleton.publicMethod();
// 再次获取单例对象,将得到同一个实例
const anotherInstance = Singleton.getInstance();
console.log(singleton === anotherInstance); // 输出 true
模式效果
优点
- 资源节约:由于只有一个实例,避免了频繁创建和销毁对象,节约系统资源。
- 一致性:确保系统中数据的一致性。
- 控制全局访问:可以控制实例的访问方式,保证系统中只有一个共享实例。
缺点
- 全局状态:单例模式可能会导致全局状态的维护,增加测试和维护的难度。
- 扩展性:由于只能创建一个实例,可能会限制类的扩展。
- 多线程问题:在多线程环境中需要额外的处理来保证线程安全(尽管 JavaScript 是单线程的,但要注意在其他语言中的并发问题)。
应用场景
- 系统只需要一个实例,如配置文件读取器、数据库连接池、日志管理器等。
- 需要一个全局访问点来获取唯一的实例。
模式应用
1. jQuery
jQuery
虽然不是真正的单例模式,但它在使用中表现出单例模式的特点:
- 全局访问点:通过
$
符号或jQuery
提供了全局访问点。 - 唯一实例:无论何时调用
jQuery()
或$()
,都会返回相同的 jQuery 对象。
// 使用 jQuery 选择 DOM 元素
const myjQueryObject = $("<div>Hi, I'm a jQuery object!</div>");
// 全局访问 jQuery 对象
console.log(jQuery); // 输出 jQuery 对象
console.log($); // 输出 jQuery 对象
2. Pinia
Pinia
是 Vue 3 的官方状态管理库,展示了单例模式的特性:
- 单个 Store 实例:每个通过
defineStore
创建的 Store 都是一个单例。 - 全局状态访问:Pinia 提供
useStore
钩子,允许在应用的任何组件中访问同一个 Store 实例。
// 定义 Store
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
double: (state) => state.count * 2,
},
actions: {
increment() {
this.count++;
},
},
});
// 使用 Store
<script setup>
import { useCounterStore } from '@/stores/counter';
const store = useCounterStore();
</script>
总结
单例模式确保类的实例唯一性,并提供一个全局访问点。它适用于需要全局共享资源的场景,如配置管理器、数据库连接池等。然而,单例模式也可能导致全局状态难以管理、扩展性受限的问题。在 JavaScript 的很多库中(如 jQuery、Pinia),我们可以看到单例模式的实际应用。
单例模式优缺点明显,需要在使用时结合实际需求权衡利弊。