今天学习设计模式中的单例模式时,我突然顿悟了------这不就是编程界的"全球限量版"嘛!就像世界上只有一个蒙娜丽莎原画,一个埃菲尔铁塔,在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) { /* ... */ }
}
这个设计就像中世纪的王位继承:
static instance
是那个空置的王座getInstance()
是加冕仪式------第一次举行时选出国王- 后续所有"觐见请求"都直接拜见同一位国王
在这个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的秘密任务:
- 立即执行函数创建了一个绝密保险箱(闭包作用域)
instance
是保险箱里唯一存放的微缩国王雕像- 每次执行
new Storage()
相当于特工来取雕像 - 特工第一次来才现场制作雕像,之后都返回同一个
使用效果同样符合预期:
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是个公共储物柜:
-
普通模式:每次访问都造个新柜子钥匙(创建实例)
- 浪费金属资源(内存)
- 可能忘记关柜门(状态不一致)
-
单例模式:全公司共用一把万能钥匙(单例)
- 节省金属资源(内存优化)
- 永远存取同一个柜子(状态一致)
- 由行政部统一管理钥匙(易于维护)
尤其在需要全局状态管理的场景中,单例模式优势尽显:
- 配置信息管理器
- 应用状态仓库
- 缓存控制器
- 日志记录器
六、单例的"防伪标识"
如何确保你的单例货真价实?两个关键特征:
-
私有构造器 :防止外部随意
new
arduino// 在Class版本中,我们通过不暴露构造函数实现 const s1 = Storage.getInstance(); // ✅正确方式 const s2 = new Storage(); // ❌禁止直接new
-
全局访问点:提供统一获取入口
scss// 就像公司唯一的总机号码 Storage.getInstance();
这也是我们上述提到的,不能通过new
实例化,而是通过getInstance()
,但是闭包写法是不同的,因为我们返回是一个实例化对象
七、使用注意事项:能力越大,责任越大
单例虽好,但切勿滥用,小心变成"代码独裁者":
- 避免变成上帝对象:不要把所有功能塞进单例
- 注意内存泄漏:长期存在的实例需及时清理内部状态
- 多线程问题:在JS中虽无此虑,但其他语言需考虑
记住编程界的"汉堡包原则":单例像顶层的面包片,全局可见但应尽量薄;业务逻辑像中间的馅料,应该分层封装。
结语:设计模式的哲学
单例模式教会我们一个深刻哲理------有时候限制才是真正的自由。通过约束实例数量,我们反而获得了:
- 更可控的资源管理 🎮
- 更一致的行为表现 🎯
- 更简洁的调用方式 🚀
就像生活中最珍贵的往往是限量版物品,代码中最优雅的也常常是精心设计的约束。当你下次看到全局状态管理器时,不妨会心一笑:"啊,原来是个戴着王冠的单例陛下!"