🌱 基础篇:什么是单例模式?
🧠 想象场景:你和朋友在奶茶店点单,如果每来一个顾客就开一家分店,会出现什么问题?
// ❌ 错误示范:多实例地狱
const branch1 = new Storage();
const branch2 = new Storage();
branch1.setItem('爱珍珠还是爱乡乡', '爱乡乡');
console.log(branch2.getItem('爱珍珠还是爱乡乡')); // 猜猜会输出什么?🤯
💡 答案揭晓:
由于不是单例模式,branch2会创建一个新的"珍珠库存",导致数据不一致!就像两家奶茶店各自进货,顾客永远不知道哪家有珍珠😅
🎯 正确做法:
// ✅ 单例模式保障
const mainStore = Storage.getInstance();
mainStore.setItem('爱珍珠还是爱乡乡', '爱乡乡');
console.log(Storage.getInstance().getItem('爱珍珠还是爱乡乡')); // 100%显示'爱乡乡'!
🌟 单例模式三要素:
- 唯一性:全局只有一个实例(就像奶茶店总店)
- 全局访问点:通过
Storage.getInstance()获取(类似保险箱的钥匙) - 延迟初始化:首次调用时才创建实例(节省内存)
🔧 进阶篇:两种实现方式大比拼
方式一:构造函数方式
class Storage {
constructor() {
if (Storage.instance) {
return Storage.instance; // ⚠️ 关键点:重复调用返回已有实例
}
this.storage = window.localStorage;
Storage.instance = this; // ✅ 保存唯一实例
}
}
方式二:静态方法方式
class Storage {
static getInstance() {
if (!Storage.instance) {
Storage.instance = new Storage(); // 🔄 只创建一次
}
return Storage.instance;
}
}
🧠 对比分析:
| 特性 | 构造函数方式 | 静态方法方式 |
|---|---|---|
| 实例创建时机 | 第一次new时 | 第一次调用getInstance时 |
| 内存效率 | 优秀 | 优秀 |
| 代码可读性 | 中等(需注意new的陷阱) | 高(明确的工厂方法) |
🧠 深度解析:饿汉式 vs 懒汉式
饿汉式(Eager Initialization)
定义:类加载时立即创建实例
优点:
- 线程安全(无需额外同步)
- 实现简单,性能高
缺点: - 可能造成资源浪费(即使未被使用)
class Singleton {
constructor() {
this.data = "Singleton Data";
}
static getInstance() {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}
return Singleton.instance;
}
}
// 饿汉式:类加载时立即创建
Singleton.instance = new Singleton();
懒汉式(Lazy Initialization)
定义:首次调用 getInstance() 时才创建实例
优点:
- 资源利用率高(按需加载)
缺点: - 多线程环境下需处理线程安全(JS中通常无需考虑)
class Singleton {
constructor() {
this.data = "Lazy Singleton Data";
}
static getInstance() {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}
return Singleton.instance;
}
}
🧠 对比分析:
| 特性 | 饿汉式 | 懒汉式 |
|---|---|---|
| 实例创建时机 | 类加载时立即创建 | 首次调用 getInstance() 时创建 |
| 线程安全 | 安全(无需额外同步) | 需要额外处理(JS中通常无需) |
| 资源占用 | 可能浪费(即使未使用) | 按需加载,节省资源 |
| 适用场景 | 实例创建成本低,且必用 | 实例创建成本高,或可能不被使用 |
🧪 实战篇:LocalStorage封装实战
🛠️ Storage.js完整实现
class Storage {
constructor() {
if (Storage.instance) {
return Storage.instance;
}
this.storage = window.localStorage;
Storage.instance = this;
}
setItem(key, value) {
try {
const jsonValue = JSON.stringify(value); // 💡 自动序列化
this.storage.setItem(key, jsonValue);
return true;
} catch (error) {
console.error("存储失败:", error);
return false;
}
}
getItem(key) {
try {
const value = this.storage.getItem(key);
if (value === null) return null;
return JSON.parse(value); // 🔄 自动反序列化
} catch (error) {
console.error("读取失败:", error);
return this.storage.getItem(key);
}
}
// 其他方法...
}
export default new Storage(); // 🚨 导出单例实例!
🧭 使用示例
// ✅ 正确写法
import storage from './Storage.js';
storage.setItem('user', { name: '乡乡', age: 20 });
console.log(storage.getItem('user').age); // 20
// ❌ 错误写法
const s1 = new Storage();
const s2 = new Storage();
console.log(s1 === s2); // false!双胞胎打架了💥
🚨 常见陷阱:这些坑千万别踩!
陷阱1:误用new关键字
// ❌ 多实例地狱
const s1 = new Storage();
const s2 = new Storage();
console.log(s1 === s2); // false!数据不一致⚠️
陷阱2:忘记导出单例
// ❌ 错误导出
export class Storage { ... }
// ✅ 正确导出
export default new Storage();
陷阱3:未处理JSON转换
// ❌ 存储对象会变成"[object Object]"
storage.setItem('user', user);
// ✅ 正确方式(自动处理)
storage.setItem('user', { name: '小红' });
🛠️ 验证机制:如何证明是单例?
🧪 单例验证测试
const s1 = Storage.getInstance();
const s2 = Storage.getInstance();
console.log(s1 === s2); // true ✅
// 查看内存地址(Chrome开发者工具)
console.log(s1);
console.log(s2);
🖥️ Chrome开发者工具操作指南
- 打开开发者工具(F12)
- 切换到Application标签页
- 在LocalStorage中查看数据
- 比较不同实例的存储内容
🧠 读者思考环节
思考题1:猜猜输出什么?
const s = Storage.getInstance();
s.setItem('count', 10);
console.log(s.getItem('count')); // ?
思考题2:如何修复这个错误?
function initApp() {
const s = new Storage(); // ❌ 错误写法
s.setItem('theme', 'dark');
}
思考题3:为什么推荐静态方法?
// 选项A: new Storage()
// 选项B: Storage.getInstance()
// 选项C: Storage()
📚 附录:完整运行示例
example.html
<!DOCTYPE html>
<html>
<body>
<script type="module">
import storage from './Storage.js';
// 测试单例
const s1 = storage;
const s2 = storage;
console.log(s1 === s2); // true
// 测试数据存储
s1.setItem('test', { key: 'value' });
console.log(s2.getItem('test')); // { key: 'value' }
</script>
</body>
</html>
🌟 总结:单例模式的价值
| 优势 | 生活类比 |
|---|---|
| 数据一致性 | 奶茶店统一库存 |
| 内存高效 | 一台咖啡机服务所有人 |
| 全局访问 | 公司唯一打卡机 |
| 易于维护 | 单一管理系统 |
通过本教程,你已经掌握了:
- 单例模式的核心思想(唯一钥匙🔑)
- 两种实现方式的对比(工厂模式 vs 构造函数)
- 饿汉式与懒汉式的实现与区别
- LocalStorage的优雅封装
- 避免常见陷阱的技巧
现在,快去你的项目中实践这些知识吧!记得用Storage.getInstance()而不是new Storage()哦~ 😄