在现代前端开发中,设计模式 是构建大型、可维护项目的重要工具之一。其中,单例模式(Singleton Pattern) 是最常见、也是最实用的一种创建型设计模式。
一、什么是单例模式?
单例模式(Singleton Pattern) 是一种设计模式,它的核心思想是:
确保一个类在整个程序运行期间只能被实例化一次,并提供一个全局访问点来获取这个唯一实例。
无论你调用多少次“创建对象”的方法,拿到的始终是同一个对象。
📌 举个生活中的例子:
想象你在使用 Windows 系统时打开了“任务管理器”。无论你点击打开多少次,系统都只会显示一个窗口。这就是单例模式的典型应用。
二、为什么需要单例模式?
单例模式适用于那些需要全局共享状态或资源控制的场景,例如:
| 场景 | 说明 |
|---|---|
| 日志记录器 | 多个模块都要写日志,但只需一个日志对象统一处理 |
| 全局状态管理 | 用户登录状态、主题设置等,整个系统共享一份数据 |
| 数据库连接池 | 创建连接很耗资源,只创建一个池子来统一管理 |
| 网站计数器 | 统一统计用户访问量,避免多个计数器冲突 |
三、实现目标:封装一个基于 localStorage 的单例 Storage 类
我们将实现一个名为 Storage 的类,具备以下功能:
- 提供两个方法:
setItem(key, value):将数据写入浏览器本地存储getItem(key):从本地存储中读取数据
- 这个类必须是单例:无论调用多少次,返回的都是同一个对象
我们将分别使用两种方式实现它:
- 使用
class类(推荐) - 使用
函数 + 闭包(适合封装库)
四、方法一:使用 class 类实现单例
这是现代 JavaScript 中最常见、也最容易理解的实现方式,适用于 React、Vue、Angular 等主流前端框架。
示例代码:
class Storage {
static instance;
constructor() {
// 如果已经存在实例,则直接返回已存在的实例
if (Storage.instance) {
return Storage.instance;
}
Storage.instance = this;
}
// 静态方法用于获取唯一实例
static getInstance() {
if (!Storage.instance) {
Storage.instance = new Storage();
}
return Storage.instance;
}
// 获取本地存储中的值
getItem(key) {
return localStorage.getItem(key);
}
// 设置本地存储中的值
setItem(key, value) {
localStorage.setItem(key, value);
}
}
使用方式:
const storage1 = Storage.getInstance();
const storage2 = Storage.getInstance();
console.log(storage1 === storage2); // true
storage1.setItem('name', 'lisi');
console.log(storage2.getItem('name')); // lisi
特点说明:
static instance:保存唯一的实例constructor:防止重复实例化static getInstance():暴露给外部获取唯一实例的方法getItem / setItem:封装了对localStorage的操作
优点:
| 优点 | 说明 |
|---|---|
| 可读性高 | 是 ES6 标准语法,结构清晰 |
| 易于扩展 | 可以轻松添加更多功能,如 clear()、onUpdate() 等 |
| 易于测试 | 支持 mock、替换等测试手段 |
五、方法二:使用函数 + 闭包实现单例(适合封装库)
如果你是在开发一个插件、库或者工具模块,这种实现方式可以更好地保护内部变量不被外部修改。
示例代码:
function StorageBase() {}
// 原型方法:获取数据
StorageBase.prototype.getItem = function(key) {
return localStorage.getItem(key);
};
// 原型方法:设置数据
StorageBase.prototype.setItem = function(key, value) {
localStorage.setItem(key, value);
};
// 使用闭包 + IIFE 创建单例工厂函数
const storage = (function () {
let instance = null;
return function () {
if (!instance) {
instance = new StorageBase();
}
return instance;
};
})();
使用方式:
const storage1 = new storage();
const storage2 = new storage();
console.log(storage1 === storage2); // true
storage1.setItem('name', 'zhangsan');
console.log(storage2.getItem('name')); // zhangsan
console.log(storage1.getItem('name')); // zhangsan
特点说明:
(function(){ ... })():这是一个立即执行函数(IIFE),用来创建一个封闭的作用域let instance = null:这个变量被闭包保护,不会被垃圾回收- 返回的函数就是工厂函数,负责控制实例的创建过程
- 外部无法直接访问或修改
instance,只能通过storage()获取实例
优点:
| 优点 | 说明 |
|---|---|
| 封装性强 | instance 被闭包保护,外部无法修改 |
| 安全性高 | 变量私有化,只能通过工厂函数访问 |
| 更适合库开发 | 适合封装成模块或插件 |
六、哪种方式更好?
| 对比项 | class 类 | 函数 + 闭包 |
|---|---|---|
| 可读性 | ✅ 高 | ❌ 稍低 |
| 封装性 | ❌ 一般 | ✅ 强 |
| 易于扩展 | ✅ 强 | ❌ 稍弱 |
| 适合项目 | ✅ React/Vue/Angular | ✅ 插件/库开发 |
| 推荐程度 | ✅ 推荐新手使用 | ✅ 推荐封装库使用 |
结论:
- 如果你正在做一个现代前端项目(React/Vue/Angular),推荐使用 class 类 实现单例。
- 如果你在开发一个库、插件或者需要更高的封装性和安全性,推荐使用 函数 + 闭包 实现单例。
七、单例模式的应用场景总结
| 场景 | 说明 |
|---|---|
| 日志记录器 | 多个模块都要记录日志,但只需要一个日志对象 |
| 全局状态管理 | 如用户登录状态、主题设置等,整个系统共享一个状态 |
| 数据库连接池 | 创建连接很耗资源,只创建一个池管理连接 |
| 网站计数器 | 多个用户访问,但只能有一个计数器对象 |
单例模式是一种确保一个类只有一个实例的设计模式,适用于全局状态管理、日志记录、数据库连接等场景。在 JavaScript 中,可以用
class或函数 + 闭包两种方式实现,前者适合现代前端项目,后者适合库级封装。
单例的本质是“保证只有一个实例,并提供统一访问入口”。