JavaScript 单例模式实战:打造一个全局唯一的 Storage 类

113 阅读5分钟

在现代前端开发中,设计模式 是构建大型、可维护项目的重要工具之一。其中,单例模式(Singleton Pattern) 是最常见、也是最实用的一种创建型设计模式。

一、什么是单例模式?

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

确保一个类在整个程序运行期间只能被实例化一次,并提供一个全局访问点来获取这个唯一实例。

无论你调用多少次“创建对象”的方法,拿到的始终是同一个对象。

📌 举个生活中的例子:

想象你在使用 Windows 系统时打开了“任务管理器”。无论你点击打开多少次,系统都只会显示一个窗口。这就是单例模式的典型应用。


二、为什么需要单例模式?

单例模式适用于那些需要全局共享状态或资源控制的场景,例如:

场景说明
日志记录器多个模块都要写日志,但只需一个日志对象统一处理
全局状态管理用户登录状态、主题设置等,整个系统共享一份数据
数据库连接池创建连接很耗资源,只创建一个池子来统一管理
网站计数器统一统计用户访问量,避免多个计数器冲突

三、实现目标:封装一个基于 localStorage 的单例 Storage

我们将实现一个名为 Storage 的类,具备以下功能:

  • 提供两个方法:
    • setItem(key, value):将数据写入浏览器本地存储
    • getItem(key):从本地存储中读取数据
  • 这个类必须是单例:无论调用多少次,返回的都是同一个对象

我们将分别使用两种方式实现它:

  1. 使用 class 类(推荐)
  2. 使用 函数 + 闭包(适合封装库)

四、方法一:使用 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函数 + 闭包 两种方式实现,前者适合现代前端项目,后者适合库级封装。

单例的本质是“保证只有一个实例,并提供统一访问入口”。