🧊 前端必备设计模式|手把手封装一个基于 localStorage 的单例模式 Storage 工具类

74 阅读4分钟

在日常开发中,我们常常需要频繁地与 localStorage 打交道,例如保存用户 token、用户偏好设置、缓存数据等。
但你是否曾遇到下面这种重复代码、杂乱逻辑?

localStorage.setItem('token', 'abc123');
const token = localStorage.getItem('token');

代码散落各处、无统一封装、不易维护,还可能多次创建重复工具对象。

这时候,就该请出我们前端工程化的老朋友 —— 单例模式(Singleton Pattern)

本篇文章将由浅入深带你理解什么是单例模式,为什么使用它,以及如何用它优雅封装一个 Storage 工具类。✨


🧩 一、什么是单例模式?

单例模式(Singleton Pattern) 是一种常见的设计模式,其核心思想是:

一个类只能被实例化一次,并提供一个全局唯一访问点。

这就好比我们系统中只允许一个“全局配置管理器”或“数据库连接”,避免重复创建造成资源浪费或状态冲突。

🧠 单例模式的三要素:

  1. 私有构造函数:禁止外部用 new 随意创建;
  2. 静态属性存储实例:保存已经创建的唯一实例;
  3. 静态方法获取实例:统一入口,重复获取不会重新创建。

🚀 二、为什么要用单例封装 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);
}

✅ 六、总结

🌟 为什么推荐你掌握并使用单例模式?

单例模式有很多实际优势,下面用文字详细说明一下:

  • 节省内存:由于只创建一个实例,可以有效减少资源开销,避免多个对象重复占用内存。
  • 全局一致:无论在哪个模块或组件中调用,获取到的都是同一个对象,保证数据状态一致。
  • 易于管理:所有存取逻辑都集中在这个类中,方便统一维护和更新,避免分散在各个角落。
  • 封装性强:通过 getItemsetItem 等方法,屏蔽了底层实现,对外提供清晰简洁的接口。
  • 扩展性高:可以轻松加入新功能,比如命名空间、数据过期机制、自动同步等。

📌 最后一问:模块化天然就是单例吗?

是的!你甚至可以直接这样写:

// storage.js
class Storage {
  // ... 同上
}
const storage = new Storage();
export default storage;

在 ES Module 中,导出的对象或类实例默认就是单例,因为模块只会被加载一次,后续都是缓存。


💬 写在最后

单例模式不仅仅是高级程序员交流的语言,更是你提升前端架构能力的必经之路。
它能帮你写出更优雅、更高效、更可维护的代码。 希望本文能帮你理解并灵活使用单例模式,写出更具专业度的前端代码。 如果你觉得有帮助,欢迎点赞 👍 收藏 ⭐ 留言交流 💬!