在日常开发中,我们常常需要频繁地与 localStorage 打交道,例如保存用户 token、用户偏好设置、缓存数据等。
但你是否曾遇到下面这种重复代码、杂乱逻辑?
localStorage.setItem('token', 'abc123');
const token = localStorage.getItem('token');
代码散落各处、无统一封装、不易维护,还可能多次创建重复工具对象。
这时候,就该请出我们前端工程化的老朋友 —— 单例模式(Singleton Pattern) 。
本篇文章将由浅入深带你理解什么是单例模式,为什么使用它,以及如何用它优雅封装一个
Storage工具类。✨
🧩 一、什么是单例模式?
单例模式(Singleton Pattern) 是一种常见的设计模式,其核心思想是:
一个类只能被实例化一次,并提供一个全局唯一访问点。
这就好比我们系统中只允许一个“全局配置管理器”或“数据库连接”,避免重复创建造成资源浪费或状态冲突。
🧠 单例模式的三要素:
- 私有构造函数:禁止外部用
new随意创建; - 静态属性存储实例:保存已经创建的唯一实例;
- 静态方法获取实例:统一入口,重复获取不会重新创建。
🚀 二、为什么要用单例封装 localStorage?
🤯 普通写法的问题:
- 每次都重复调用原生 API,代码零散;
- 可能多处创建 storage 工具对象,不一致;
- 没有统一封装,难维护、难扩展;
- 缺少容错和 JSON 封装。
✅ 用单例模式封装后的优势:
- 全局只有一个实例,节省内存;
- 更容易统一管理存取逻辑;
- API 简洁易用,维护扩展方便;
- 天然适合和模块化搭配使用。
✏️ 三、一步步实现 Storage 单例类
我们来构建一个基于 localStorage 的单例类 Storage,具备以下特点:
- 全局只存在一个实例;
- 提供
setItem(key, value)和getItem(key)两个方法; - 自动进行 JSON 序列化和反序列化;
- 避免重复创建对象,确保状态一致;
✅ 实现代码如下:
class Storage {
// 用静态属性保存唯一实例
static instance = null;
// 私有构造函数,外部无法直接 new 创建多个
constructor() {
if (Storage.instance) {
return Storage.instance;
}
Storage.instance = this;
}
// 封装 setItem:自动 JSON 序列化
setItem(key, value) {
localStorage.setItem(key, JSON.stringify(value));
}
// 封装 getItem:自动 JSON 反序列化,容错处理
getItem(key) {
const value = localStorage.getItem(key);
try {
return JSON.parse(value);
} catch (e) {
return value;
}
}
// 提供静态方法,作为唯一获取实例的入口
static getInstance() {
if (!Storage.instance) {
Storage.instance = new Storage();
}
return Storage.instance;
}
}
🧪 四、如何使用?
const storage1 = Storage.getInstance();
const storage2 = Storage.getInstance();
storage1.setItem('username', 'jiangyufeng');
console.log(storage2.getItem('username')); // jiangyufeng
console.log(storage1 === storage2); // true ✅ 同一个实例
我们成功创建了一个 全局唯一 的 localStorage 封装实例,任何组件中都可以直接通过
Storage.getInstance()进行访问。
📦 五、可以扩展什么功能?
这个单例类也非常方便扩展,例如你可以加上:
removeItem(key)删除数据;clear()清空所有 localStorage;- 命名空间前缀(避免 key 冲突);
- 过期时间(支持缓存控制);
举个例子,新增一个 removeItem 方法非常简单:
removeItem(key) {
localStorage.removeItem(key);
}
✅ 六、总结
🌟 为什么推荐你掌握并使用单例模式?
单例模式有很多实际优势,下面用文字详细说明一下:
- 节省内存:由于只创建一个实例,可以有效减少资源开销,避免多个对象重复占用内存。
- 全局一致:无论在哪个模块或组件中调用,获取到的都是同一个对象,保证数据状态一致。
- 易于管理:所有存取逻辑都集中在这个类中,方便统一维护和更新,避免分散在各个角落。
- 封装性强:通过
getItem、setItem等方法,屏蔽了底层实现,对外提供清晰简洁的接口。 - 扩展性高:可以轻松加入新功能,比如命名空间、数据过期机制、自动同步等。
📌 最后一问:模块化天然就是单例吗?
是的!你甚至可以直接这样写:
// storage.js
class Storage {
// ... 同上
}
const storage = new Storage();
export default storage;
在 ES Module 中,导出的对象或类实例默认就是单例,因为模块只会被加载一次,后续都是缓存。
💬 写在最后
单例模式不仅仅是高级程序员交流的语言,更是你提升前端架构能力的必经之路。
它能帮你写出更优雅、更高效、更可维护的代码。
希望本文能帮你理解并灵活使用单例模式,写出更具专业度的前端代码。
如果你觉得有帮助,欢迎点赞 👍 收藏 ⭐ 留言交流 💬!