单例模式 ⭐
定义
单例模式(Singleton Pattern),调用一个类,任何时候返回的都是同一个实例。
- 一个类只有一个实例
- 可以全局访问
“单例模式虽然简单好用,但它违背了单一职责原则(SRP)。因为它既要负责自己的核心业务逻辑,又要负责控制自己的实例化生命周期,导致测试起来非常困难(容易产生全局状态污染)。
所以在现代如 Angular 或 NestJS 等高级框架中,我们不再手写单例,而是写普通的类,然后依靠依赖注入(DI)和 IoC 容器来替我们管理单例的生命周期,这才是更优雅的设计。”
应用场景
- 弹窗、登录框
- 购物车
- 状态管理(Vuex/Pinia/Redux)
- Axios 请求实例
- EventBus
js
// http.js
import axios from 'axios';
// create 方法内部其实就是配置好并返回了一个单例
const httpInstance = axios.create({
baseURL: 'https://api.example.com',
timeout: 5000
});
httpInstance.interceptors.request.use(...);
// 导出这个唯一的实例,全项目共用
export default httpInstance;
实现
1. 基于 ES6 Class 和 static
js
class GameManager {
// 静态属性,用于保存唯一的实例
static instance = null;
constructor(name) {
// 核心拦截逻辑:如果实例已经存在,直接返回旧实例,阻止新实例的创建
if (GameManager.instance) {
return GameManager.instance;
}
this.name = name;
this.score = 0;
// 将当前实例保存到静态属性上
GameManager.instance = this;
}
// 也可以提供一个标准的静态获取方法
static getInstance(name) {
if (!this.instance) {
this.instance = new GameManager(name);
}
return this.instance;
}
}
// 测试一下:
const game1 = new GameManager('玩家A');
const game2 = new GameManager('玩家B');
console.log(game1 === game2); // true!它们是同一个对象
console.log(game2.name); // 打印 "玩家A"。(玩家B并没有创建成功)
2. 基于闭包的“透明单例”
js
const SingletonLogger = (function () {
let instance = null; // 闭包里的私有变量,用来保存实例
// 真正的构造函数
return function Logger(name) {
if (instance) {
return instance; // 如果有了,直接返回
}
this.name = name;
instance = this; // 第一次执行时,赋值给闭包变量
return this;
};
})();
const logger1 = new SingletonLogger('log1');
const logger2 = new SingletonLogger('log2');
console.log(logger1 === logger2); // true
“懒汉式” vs “饿汉式”
- 饿汉式(Eager): 只要文件一加载(类一被声明),马上就把实例创建出来。“我很饿,提前准备好吃的”。
- 懒汉式(Lazy): 文件加载时不创建实例,直到代码第一次调用 getInstance() 或者 new 的时候,才去创建。“我很懒,你推我一下我才动”。