哈喽掘友们,今天给你带来一个面试 + 实战双杀 的设计模式:
单例模式(Singleton Pattern)
别小看它,这玩意儿可是:
- 设计模式里最基础、最实用的一个。
- 你要是说不出来,面试官心里:嗯?没用过 Vuex?没写过封装工具?
- 真正搞明白,工作里封装 storage、全局配置、状态管理都能用到。
别担心,今天咱直接手把手演示:
✅ 什么是单例?
✅ 为啥要单例?
✅ 怎么用 class
写单例?
✅ 怎么用 闭包 写单例?
✅ 两种写法有啥区别?
看完保证会用,还能跟面试官对吹不虚场!
什么是单例?
官方八股文:
单例模式是一种创建型设计模式 ,核心目的是:保证某个类在整个系统中只有一个实例,并提供全局访问点。
人话:
一个类只能生一个娃,全局就这一个娃谁都得用它。
单例能干嘛?
常见场景:
- 日志收集器(只有一个人负责记日志)
- 配置中心(配置信息全局共享)
- 状态管理(Vuex、Redux 本质上都是单例 store)
- 工具封装(localStorage、sessionStorage 封装)
- 连接池(后端里用得多,前端了解即可)
咱要干啥?
今天来动手封装一个**Storage
单例**,干嘛用?
- 内部基于浏览器的
localStorage
。 - 提供两个方法:
setItem(key, value)
、getItem(key)
。 - 自动序列化/反序列化,避免忘写
JSON.stringify
。
单例核心设计思路(class版)
咱先搞明白单例是怎么落地的,最常见就是:
1️⃣ 一个 static
静态属性 instance
,负责 hold 住唯一实例。
2️⃣ 一个 static getInstance()
静态方法:
- 如果没实例,就 new 一个挂上去;
- 有了就直接 return。
3️⃣ 使用时:Storage.getInstance()
永远拿到同一个对象。
✅ 上代码!Storage 单例 class
版
js
class Storage {
// 静态属性,hold 全局唯一实例
static instance = null;
// 静态方法,获取唯一实例
static getInstance() {
if (!Storage.instance) {
Storage.instance = new Storage();
}
return Storage.instance;
}
// 设置
setItem(key, value) {
localStorage.setItem(key, JSON.stringify(value));
}
// 获取
getItem(key) {
const value = localStorage.getItem(key);
try {
return JSON.parse(value);
} catch (e) {
return value;
}
}
}
// 使用
const s1 = Storage.getInstance();
const s2 = Storage.getInstance();
s1.setItem('name', '掘金小白');
console.log(s2.getItem('name')); // 掘金小白
console.log(s1 === s2); // true,确实只有一个实例
✅ 分析一下
static
:挂在类上,不在原型上,不属于实例。getInstance()
:外部想要实例,都得走这里。setItem
/getItem
:普通实例方法,挂在prototype
上,实例共享。
这就是单例最经典的写法,大厂面试只要问到 "单例",脱口而出这套就没错。
那闭包版是啥?为啥要有闭包版?
别急,闭包版也是面试爱问,原因是:
- 早期没有
class
,只能用函数 + 闭包模拟单例。 - JS 的闭包很适合保存私有变量(就是把唯一实例藏起来)。
- 有时候为了更灵活,也会用闭包来包装一些状态。
✅ 来,闭包版也走一个
思路:
- 外层一个 IIFE(立即执行函数),内部声明唯一实例
let instance
。 - 内部定义构造函数
StorageBase
。 StorageBase
构造里判断:有实例就直接 return,否则挂自己。- 返回这个构造函数。
看
js
const Storage = (function() {
let instance;
function StorageBase() {
if (instance) {
return instance;
}
instance = this;
}
StorageBase.prototype.setItem = function(key, value) {
localStorage.setItem(key, JSON.stringify(value));
};
StorageBase.prototype.getItem = function(key) {
const value = localStorage.getItem(key);
try {
return JSON.parse(value);
} catch (e) {
return value;
}
};
return StorageBase;
})();
// 使用
const s1 = new Storage();
const s2 = new Storage();
s1.setItem('job', '前端摸鱼王');
console.log(s2.getItem('job')); // 前端摸鱼王
console.log(s1 === s2); // true,闭包也 hold 住唯一实例
这俩写法有啥区别?
点 | class 版 | 闭包版 |
---|---|---|
核心依赖 | ES6 class + static |
函数 + 闭包 |
实现思路 | 静态属性存实例 | 闭包变量存实例 |
使用姿势 | Storage.getInstance() |
new Storage() |
场景 | 模块化/现代前端常用 | 老代码、灵活封装、函数式玩具 |
✅ 有啥坑要注意?
- 闭包版一定别写错!不要
StorageBase
里new StorageBase()
,不然直接死递归爆栈(很多人翻车在这)。 - 有些同学喜欢直接
export default new Storage()
,这是模块化的懒汉式,天然单例,也没问题。 - 闭包写法有点"老派",但考察你基础掌握得牢不牢。
总结一波
单例模式 = 一个类只能造一次,想用都找它要,不会多生孩子。
核心关键点
static
+getInstance()
(现代写法)- 闭包 hold 住唯一实例(经典写法)
- 用在全局唯一、状态管理、工具封装
必背台词
单例是最简单的设计模式之一,核心就是保证全局唯一实例,提供可控访问点。实际场景可以是配置中心、缓存池、日志、全局状态管理等,既节省资源又利于统一管理。
面试杀招
面试官:会单例吗?
你:会啊,用 class 的话可以用 static 属性保存唯一实例,getInstance 做懒汉式;用闭包也可以 hold 实例,外面拿不到内部状态,保证只生成一次。
面试官:🫡,这小子还行!
写在最后
单例说简单真不难,但能写对,能讲清楚,能灵活切换实现方式,你就真的比很多人多拿一分 offer。
要是这篇帮你理清了单例,记得一键三连:
点个赞
收藏回头抄
留言:要不要我再写个「模块化 + TS 单例」的进阶版?
我是小阳,咱下次掘金再见,摸鱼人冲!