面试官:如何基于 LocalStorage 实现单例 Storage?
你:我有两种实现方案,一种用类静态方法,一种用闭包,您想先听哪种?
作为前端开发者,单例模式是面试中的高频考点。今天我们将深入探讨如何实现基于 LocalStorage 的单例 Storage,让你在面试中游刃有余!
💡 单例模式核心思想
定义 :保证一个类仅有一个实例,并提供一个全局访问点。
前端应用场景:
- 全局状态管理(如 Redux Store)
- 缓存系统(如 LocalStorage 封装)
- 模态框/弹窗管理
- 日志记录器
🚀 方法一:Class 静态方法(ES6 优雅实现)
javascript
class Storage {
// 静态属性存储唯一实例
static instance = null;
// 静态方法获取实例
static getInstance() {
if (!Storage.instance) {
Storage.instance = new Storage();
}
return Storage.instance;
}
// 实例方法封装 LocalStorage
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 ✅
⚡ 面试要点解析:
-
静态属性
instance
:- 属于类本身而非实例
- 生命周期与类相同(持久存在)
-
静态方法
getInstance()
:- 通过类直接调用(
Storage.getInstance()
) - 控制实例化逻辑(仅在第一次创建)
- 通过类直接调用(
-
为什么
new
不行?javascriptconst s1 = new Storage(); // 新实例 const s2 = new Storage(); // 另一个新实例 console.log(s1 === s2); // false ❌
- 每次
new
都会创建新对象 - 违背单例原则
- 每次
🔥 方法二:闭包实现(原型链+高阶函数)
javascript
// 基础构造函数
function StorageBase() {}
// 原型方法封装 LocalStorage
StorageBase.prototype.getItem = function(key) {
return localStorage.getItem(key);
};
StorageBase.prototype.setItem = function(key, value) {
localStorage.setItem(key, value);
};
// 闭包实现单例
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 ✅
⚡ 面试要点解析:
-
闭包与自由变量:
instance
被内部函数捕获(自由变量)- 持久保存唯一实例
-
为什么
new
有效?
new
操作符的行为:javascriptfunction MyClass() { // 默认返回 this(新创建的对象) // 但如果显式返回对象,则覆盖默认行为 }
- 内部函数显式返回
instance
(对象) - 覆盖了
new
的默认返回值
- 内部函数显式返回
-
执行流程解析:
graph LR A[new Storage] --> B[执行闭包返回的函数] B --> C{instance存在?} C -->|否| D[创建StorageBase实例] C -->|是| E[返回现有instance]
💎 两种方案对比(面试加分项)
特性 | Class 静态方法 | 闭包实现 |
---|---|---|
原理 | 静态属性存储实例 | 闭包捕获自由变量 |
实例化方式 | Storage.getInstance() |
new Storage() |
是否支持new |
不支持(会创建多个实例) | 支持(覆盖new 行为) |
封装性 | 高(所有逻辑在类内部) | 中(需额外构造函数) |
内存管理 | 类卸载时实例回收 | 闭包持久化(需手动解除引用) |
适用场景 | ES6+ 项目 | 兼容旧浏览器/函数式编程场景 |
⚠️ 单例模式常见面试陷阱
-
循环引用问题
javascript// 错误示范:在类内部直接调用getInstance() class Storage { constructor() { this.instance = Storage.getInstance(); // 循环调用 } }
✅ 正确做法:静态方法应完全独立于实例
-
多线程问题(前端无需考虑)
前端是单线程,但Node.js环境需加锁:
javascript// Node.js 示例 static getInstance() { if (!instance) { lock.acquire(); if (!instance) instance = new Storage(); lock.release(); } return instance; }
-
实例销毁与重建
面试官可能问:"如何实现带销毁的单例?"
javascriptclass Storage { static destroy() { Storage.instance = null; } }
🌟 终极面试回答模板
面试官:"请实现基于LocalStorage的单例Storage"
你:
"我有两种主流实现方案:
- ES6 Class方案 :通过静态属性和静态方法控制实例创建,核心是
static getInstance()
保证全局唯一访问点- 闭包方案 :利用高阶函数和自由变量存储实例,关键点是覆盖
new
操作符的默认行为两种方案都能完美实现单例,区别在于:
- Class方案更符合现代编程范式
- 闭包方案兼容性更好且支持
new
语法实际项目中推荐Class方案,但理解闭包实现能更好掌握JS底层原理"
💡 知识延伸(惊艳面试官)
-
单例与模块系统的关系
javascript// 现代模块系统天然单例 export default new Storage(); // 单例导出
-
Proxy 实现惰性初始化
javascriptconst StorageProxy = new Proxy(StorageBase, { construct(target, args) { if (!instance) instance = new target(...args); return instance; } });
-
单例测试技巧
javascript// 重置单例状态便于测试 afterEach(() => Storage.instance = null);
黄金总结 :
单例模式的核心不是阻止多次实例化,而是控制实例访问入口 。
前端开发中,合理使用单例能有效管理全局状态,避免内存泄漏!