单例模式:JavaScript中的"全球限量版"对象设计术

今天学习设计模式中的单例模式时,我突然顿悟了------这不就是编程界的"全球限量版"嘛!就像世界上只有一个蒙娜丽莎原画,一个埃菲尔铁塔,在JavaScript世界里,单例模式确保某些对象也是"独苗"般的存在。让我们通过封装localStorage的案例,揭开这个神秘设计模式的面纱。

一、单例模式:程序界的"独裁者"

想象你走进一家咖啡馆,无论你点多少次"店长特调",咖啡师永远给你同一杯咖啡------不是因为他们偷懒,而是因为这杯咖啡被设计成了"全球唯一款"。这就是单例模式的精髓:一个类只能创建一个实例,后续所有操作都共享这个实例。

在Storage封装案例中,我们需要:

ini 复制代码
// 实现唯一存储对象
const storage1 = Storage.getInstance();
const storage2 = Storage.getInstance();

console.log(storage1 === storage2); // true 证明是同一个对象

二、ES6 Class版:国王的加冕仪式

先看下面代码中优雅的ES6实现方式:

javascript 复制代码
class Storage {
    static instance; // 王座(存放唯一实例)
    
    constructor() {
        console.log("国王诞生记...");
    }
    
    static getInstance() { // 加冕仪式
        if (!Storage.instance) {
            Storage.instance = new Storage(); // 第一次调用时创建国王
        }
        return Storage.instance; // 永远返回同一位国王
    }
    
    // 实际功能方法
    getItem(key) { /* ... */ }
    setItem(key, value) { /* ... */ }
}

这个设计就像中世纪的王位继承:

  1. static instance 是那个空置的王座
  2. getInstance() 是加冕仪式------第一次举行时选出国王
  3. 后续所有"觐见请求"都直接拜见同一位国王

在这个Storage类中,必须通过getInstance()实例化,我们把静态属性实例化,第一次调用不存在实例,我们进行实例化,之后每次调用getInstance()返回都是static instance

测试结果证明其有效性:

arduino 复制代码
console.log(storage1 === storage2, '////');
storage1.setItem('name', '张三');
console.log(storage1.getItem('name'), '////');
console.log(storage2.getItem('name'), '////');

三、闭包实现版:藏在保险柜里的秘密

再看下述代码中充满JavaScript特色的闭包实现:

javascript 复制代码
// 先定义基础构造函数(相当于国王的DNA)
function StorageBase() {}
StorageBase.prototype.getItem = function(key) { /* ... */ };
StorageBase.prototype.setItem = function(key, value) { /* ... */ };

// 单例魔法在此发生
const Storage = (function() {
    let instance = null; // 藏在闭包保险柜里的国王
    
    return function() {
        if (!instance) {
            instance = new StorageBase(); // 保险柜空时才造国王
        }
        return instance;
    }
})();

这个实现就像007的秘密任务:

  1. 立即执行函数创建了一个绝密保险箱(闭包作用域)
  2. instance 是保险箱里唯一存放的微缩国王雕像
  3. 每次执行new Storage()相当于特工来取雕像
  4. 特工第一次来才现场制作雕像,之后都返回同一个

使用效果同样符合预期:

arduino 复制代码
const storage1 = new Storage();
const storage2 = new Storage();
console.log(storage1 === storage2, '////');
storage1.setItem('name', '王五');
console.log(storage1.getItem('name'), '////');
console.log(storage2.getItem('name'), '////')

相信大家可能会疑惑不是不能new吗?确实时不能new但是我们返回的是一个实例化类,之前我们手写new 的适合探究过,构造函数由返回值,并且返回是对象的时候,那么我们new的实例化对象的值,就是我们返回的对象

大家可以看看我的这篇文章,这里面由关于这方面的知识点当JavaScript的new操作符开始内卷:手写实现背后的奇妙冒险

四、两大门派比武大会

特性 ES6 Class派 闭包派
核心原理 静态属性存储实例 闭包变量存储实例
实例化方式 getInstance()静态方法 常规new调用
原型扩展 Class语法 显式操作prototype
代码可读性 更符合传统OOP 更体现JS函数式特性
内存管理 类定义时初始化静态属性 调用时才创建闭包

就像金庸小说里的气宗与剑宗,两种实现各有千秋:

  • Class派 像是名门正派,招式规范,适合大型项目
  • 闭包派 像是逍遥派,潇洒飘逸,展现JS灵活特性

五、为什么需要单例?举个栗子🌰

想象localStorage是个公共储物柜:

  1. 普通模式:每次访问都造个新柜子钥匙(创建实例)

    • 浪费金属资源(内存)
    • 可能忘记关柜门(状态不一致)
  2. 单例模式:全公司共用一把万能钥匙(单例)

    • 节省金属资源(内存优化)
    • 永远存取同一个柜子(状态一致)
    • 由行政部统一管理钥匙(易于维护)

尤其在需要全局状态管理的场景中,单例模式优势尽显:

  • 配置信息管理器
  • 应用状态仓库
  • 缓存控制器
  • 日志记录器

六、单例的"防伪标识"

如何确保你的单例货真价实?两个关键特征:

  1. 私有构造器 :防止外部随意new

    arduino 复制代码
    // 在Class版本中,我们通过不暴露构造函数实现
    const s1 = Storage.getInstance(); // ✅正确方式
    const s2 = new Storage(); // ❌禁止直接new
  2. 全局访问点:提供统一获取入口

    scss 复制代码
    // 就像公司唯一的总机号码
    Storage.getInstance();

这也是我们上述提到的,不能通过new实例化,而是通过getInstance(),但是闭包写法是不同的,因为我们返回是一个实例化对象

七、使用注意事项:能力越大,责任越大

单例虽好,但切勿滥用,小心变成"代码独裁者":

  1. 避免变成上帝对象:不要把所有功能塞进单例
  2. 注意内存泄漏:长期存在的实例需及时清理内部状态
  3. 多线程问题:在JS中虽无此虑,但其他语言需考虑

记住编程界的"汉堡包原则":单例像顶层的面包片,全局可见但应尽量薄;业务逻辑像中间的馅料,应该分层封装。

结语:设计模式的哲学

单例模式教会我们一个深刻哲理------有时候限制才是真正的自由。通过约束实例数量,我们反而获得了:

  • 更可控的资源管理 🎮
  • 更一致的行为表现 🎯
  • 更简洁的调用方式 🚀

就像生活中最珍贵的往往是限量版物品,代码中最优雅的也常常是精心设计的约束。当你下次看到全局状态管理器时,不妨会心一笑:"啊,原来是个戴着王冠的单例陛下!"

相关推荐
慧慧吖@12 分钟前
关于两种网络攻击方式XSS和CSRF
前端·xss·csrf
徐小夕25 分钟前
失业半年,写了一款多维表格编辑器pxcharts
前端·react.js·架构
LaoZhangAI1 小时前
Kiro vs Cursor:2025年AI编程IDE深度对比
前端·后端
止观止1 小时前
CSS3 粘性定位解析:position sticky
前端·css·css3
爱编程的喵2 小时前
深入理解JavaScript单例模式:从Storage封装到Modal弹窗的实战应用
前端·javascript
lemon_sjdk2 小时前
Java飞机大战小游戏(升级版)
java·前端·python
G等你下课2 小时前
如何用 useReducer + useContext 构建全局状态管理
前端·react.js
欧阳天羲2 小时前
AI 增强大前端数据加密与隐私保护:技术实现与合规遵
前端·人工智能·状态模式
慧一居士2 小时前
Axios 和Express 区别对比
前端