Q1:
大厂前端面试中关于设计模式的知识要怎么总结
思考过程(Thinking):
用户想了解如何总结大厂前端面试中的设计模式知识。我需要提供一份全面的指南,涵盖高频考察的设计模式、学习策略、总结方法以及面试应答技巧。为了全面获取信息,我将同时进行多项搜索,以覆盖不同的关键词和侧重点。
A:
🎯 一、明确面试官的考察意图:不只是背八股文
面试官考察设计模式,真正关心的是以下三点:
- 你是否理解问题的本质:能识别业务场景中的痛点,并判断该用什么模式解决
- 你是否能选择合适的模式:不是硬套模式,而是根据场景权衡利弊
- 你的代码设计能力:在实战中能否写出可维护、可扩展、低耦合的代码
核心原则:设计模式是工具,而不是目的。在面试中展示你如何根据实际问题选择合适的模式,这才是面试官真正想看到的。
🗂️ 二、高频设计模式:按优先级分层总结
📌 第一优先级:创建型模式(大厂最爱考)
大厂面试中考察的重点就是创建型设计模式,比如工厂模式、抽象工厂模式和单例模式,是校招和社招中比较常见的问题。创建型模式主要解决「如何创建对象」的问题,简化创建过程,降低耦合度。
| 模式 | 一句话理解 | 前端应用场景 | 手写实现要点 |
|---|---|---|---|
| 单例模式 | 全局只有一个实例 | Redux/Vuex的Store、全局弹窗管理、日志记录器 | 用闭包或静态属性缓存实例,重复创建时返回已有实例 |
| 工厂模式 | 用一个函数专门负责创建对象 | 根据类型创建不同UI组件、Axios实例化、不同权限的菜单 | 封装new逻辑,统一创建入口 |
| 建造者模式 | 分步骤构建复杂对象 | 复杂配置对象的构建(如请求参数组装) | 链式调用 + 逐步构建 |
💡 单例模式懒汉/饿汉区别(Java体系面试常延伸):
- 饿汉模式:类初始化时就创建实例,线程安全,但可能造成资源浪费
- 懒汉模式:首次使用时才创建实例,需注意线程安全问题(双检锁)
📌 第二优先级:行为型模式(框架原理高频)
行为型模式主要解决「对象之间怎么协作通信」的问题。大厂特别喜欢考察这些模式与Vue/React框架的结合理解。
| 模式 | 一句话理解 | 前端应用场景 | 面试高频追问 |
|---|---|---|---|
| 观察者模式 vs 发布-订阅模式 | 观察者直接通知订阅者 vs 通过事件中心解耦 | Vue响应式系统(观察者)、EventBus(发布-订阅) | 区别是什么? 观察者中观察者和主体知道彼此;发布-订阅中通过消息代理通信,耦合度更低 |
| 策略模式 | 定义一系列算法,封装起来可以互换 | 表单验证规则、支付方式处理、排序算法切换 | 避免大量if-else,符合开闭原则 |
| 模板方法模式 | 定义算法骨架,子类重写特定步骤 | 框架生命周期钩子、通用流程封装 | 好莱坞原则:「别调用我们,我们会调用你」 |
📌 第三优先级:结构型模式(加分项)
结构型模式主要解决「对象之间怎么组织结构」的问题。
| 模式 | 一句话理解 | 前端应用场景 |
|---|---|---|
| 代理模式 | 为对象找个中间人控制访问 | Vue3的Proxy响应式系统、图片懒加载、缓存代理 |
| 装饰器模式 | 动态添加功能,不改变原结构 | React高阶组件(HOC)、日志/缓存装饰、ES7装饰器语法 |
| 适配器模式 | 兼容接口不匹配的双方 | 适配不同API响应格式、集成第三方库 |
📚 三、知识体系构建方法
🔹 Step 1:建立三大分类框架
将23种设计模式按创建型、结构型、行为型三大类系统学习:
| 类型 | 数量 | 代表模式 |
|---|---|---|
| 创建型 | 5种 | 工厂方法、抽象工厂、单例、建造者、原型 |
| 结构型 | 7种 | 适配器、装饰器、代理、外观、桥接、组合、享元 |
| 行为型 | 11种 | 策略、模板方法、观察者、迭代子、责任链、命令、备忘录、状态、访问者、中介者、解释器 |
🔹 Step 2:理解六大设计原则(SOLID)
| 原则 | 核心要点 | 面试话术 |
|---|---|---|
| 单一职责 | 一个类/函数只做一件事 | 「我设计模块时会确保每个类只有一个变更理由,降低耦合」 |
| 开放封闭 | 对扩展开放,对修改封闭 | 「新增功能时优先扩展而非修改已有代码,避免引入Bug」 |
| 里氏替换 | 子类不改变父类功能 | 「继承时确保子类能完全替代父类」 |
| 依赖倒置 | 面向接口编程,不依赖具体实现 | 「我会依赖抽象而非具体类,提高模块的可替换性」 |
🔹 Step 3:总结每个模式的「三要素」
对每个高频模式,按照「定义 → 应用场景 → 代码实现」三要素来记忆和总结:
示例:单例模式
├── 定义:确保一个类只有一个实例,并提供全局访问点
├── 应用场景:全局状态管理(Redux Store)、弹窗管理器、日志记录器
└── 代码实现:闭包或静态属性缓存实例💬 四、面试应答技巧:三步结构化回答
🔹 常见问题1:「介绍一下你工作中用到的设计模式」
回答框架:可以结合项目经历,挑选2个例子讲解,讲讲为什么用、带来了哪些优点。
✅ 推荐回答结构(STAR法则 + 设计模式):
“在X项目中,我遇到了【场景】,存在【痛点】。我选择了【模式名称】来解决,因为【选择理由】。
具体实现是【简要描述实现方式】。使用后带来的好处是:
- 可维护性:…
- 扩展性:…
- 代码复用:…
当然也带来了一些【权衡/代价】,比如增加了代码复杂度。”✅ 示例回答(参考真实面试经验):
“我在优化登录代码时,使用了观察者模式。之前的登录代码是面条式的,一行代码处理记录日志、通知积分服务增加积分、通知统计服务增加登录次数等多个逻辑,导致函数臃肿且难以维护。使用观察者模式后,发一个登录成功的消息,在监听者中分别处理不同的逻辑操作,代码可维护性和扩展性得到了显著提高。”
🔹 常见问题2:「设计模式有多少种?分为哪几类?」
参考回答:GOF提出的23种设计模式,分为三大类。(接着简要列举即可,不需要全部背诵)
🔹 常见问题3:「单例模式在前端中怎么实现?有什么问题?」
参考回答:通过闭包或静态属性缓存实例。注意问题:全局状态可能导致测试困难、模块间隐式耦合。
🔹 常见问题4:「观察者模式和发布-订阅模式有什么区别?」
核心区别:
- 观察者模式:观察者和主体知道彼此存在,耦合度较高
- 发布-订阅模式:通过消息代理通信,发布者不知道订阅者的存在,更松耦合,适合大型应用
🔹 常见问题5:「谈谈你对设计模式的理解」
参考回答框架-:设计模式是在软件工程中对软件设计中普遍存在的各种问题所提出的解决方案。设计模式并不直接用来完成代码的编写,而是描述在不同情况下要怎么解决问题的方案,目的是让代码更优雅、更健壮、更可扩展。
🔧 五、复习策略与行动指南
📅 Week 1:夯实基础
- Day 1-3:学习三大分类和六大原则,理解「是什么」和「为什么」
- Day 4-7:重点攻克单例、工厂、观察者、策略四个高频模式,完成手写实现
📅 Week 2:结合框架深化理解
- 分析Vue/React源码中用到的设计模式(响应式系统、HOC、Context等)
- 整理3个自己的项目实例,准备好「工作经历+设计模式」的回答
📅 Week 3:实战演练
- 手写单例模式、发布-订阅模式、策略模式的完整代码
- 找朋友模拟面试,练习「为什么用这个模式」的表达
🔗 六、推荐学习资源
| 类型 | 推荐 |
|---|---|
| 在线教程 | 高级前端工程师必备的 JS 设计模式入门教程、前端9大设计模式全攻略 |
| 面试题库 | 大厂前端面试题库、前端面试题必刷清单 |
| 手写练习 | 设计模式手写题集锦、函数进阶&设计模式解析- |
📝 七、一句话总结
设计模式在面试中,重点不在「背出23种模式的名字」,而在于「能否用代码讲清楚一个模式解决的实际问题」。 大厂面试官想看到的是一个能识别场景、选择工具、权衡利弊的工程师,而不是一个八股文背诵机器。
Q2:
那接下来按你说的分类型分模式一步步深入浅出,通俗易懂地解释总结一下,满足面试需要的层次即可,可以分多次输出
思考过程(Thinking):
我们要求“按你说的分类型分模式一步步深入浅出,通俗易懂地解释总结一下,满足面试需要的层次即可,可以分多次输出”。用户希望我们按照之前回答中提到的分类(创建型、结构型、行为型)和模式,逐步输出。需要深入浅出、通俗易懂,满足面试需求。由于内容较多,可以分多次输出。
接下来第一次输出:先总体介绍,然后开始创建型模式的第一部分:单例模式、工厂模式。确保通俗,有例子,面试常见问题。
A:
没问题!我们按创建型 → 结构型 → 行为型的顺序,每次讲透 1~2 个模式,用大白话 + 真实场景 + 面试高频追问的方式输出。
🧱 第一部分:创建型模式(共3讲)
第1讲:单例模式 —— “唯一的我”
📌 一句话理解
一个类只能有一个实例,并提供一个全局访问点。
🎯 真实前端场景
- 全局弹窗(Toast、Modal):无论调用多少次,页面上只有一个弹窗实例,避免重复创建。
- Vuex / Redux 的 Store:整个应用只有一个数据仓库。
- 日志记录器:所有地方打日志都往同一个对象里写。
💻 手写一个最简单例(JavaScript)
const Singleton = (function() {
let instance = null;
function createInstance() {
return { name: '我是唯一的实例' };
}
return {
getInstance: function() {
if (!instance) instance = createInstance();
return instance;
}
}
})();
const a = Singleton.getInstance();
const b = Singleton.getInstance();
console.log(a === b); // true❓ 面试高频追问
Q:单例模式有什么缺点? A:全局状态可能导致模块间隐式耦合,测试时难以隔离;且在多线程环境(Node.js worker)下需要额外注意。
Q:前端单例一定用闭包吗?ES6 怎么简洁实现? A:也可以用 ES6 模块的特性——模块本身就是单例:
// logger.js
export default { log: msg => console.log(msg) };
// 任何地方 import 的都是同一个实例Q:懒汉 vs 饿汉? A:前端几乎都是“懒汉”(用到才创建),因为页面加载时不确定是否需要;饿汉(一开始就创建)适合资源固定且必定会用到的场景。
第2讲:工厂模式 —— “你要什么,我给你造”
📌 一句话理解
用一个工厂函数来创建对象,不用每次自己
new,尤其适合根据不同类型创建不同对象。
🎯 真实前端场景
- 不同权限的按钮:管理员看到“删除”按钮,普通用户看到“查看”按钮。
- 不同消息类型组件:成功、警告、错误消息用不同的样式和图标。
- Axios 实例工厂:根据 baseURL 或 token 创建不同的请求实例。
💻 简单工厂(最常用)
class ButtonAdmin { render() { return '管理员按钮' } }
class ButtonUser { render() { return '普通按钮' } }
class ButtonFactory {
createButton(role) {
switch(role) {
case 'admin': return new ButtonAdmin();
case 'user': return new ButtonUser();
default: throw new Error('未知角色');
}
}
}
const factory = new ButtonFactory();
const btn = factory.createButton('admin');❓ 面试高频追问
Q:工厂模式和直接 new 有什么区别? A:工厂将创建逻辑集中,调用方不需要知道具体类名,也方便后续扩展(比如增加 superAdmin 按钮只需改工厂,调用方无感知)。
Q:简单工厂、工厂方法、抽象工厂的区别? A:
- 简单工厂:一个工厂生产所有产品(if-else 或 switch)。
- 工厂方法:每种产品对应一个工厂子类,符合开闭原则,但类会增多。
- 抽象工厂:生产一系列相关产品(例如一套 UI 主题包含按钮、输入框、对话框)。
前端最常用的是简单工厂和工厂方法,抽象工厂在主题切换库(如 antd 的样式主题)中有体现。
下一次(第2次输出)我会接着讲创建型模式的第3讲:建造者模式,然后进入结构型模式。你想继续听吗?或者对单例/工厂还有哪些疑问,我可以当场帮你模拟面试追问。
Q3:
继续
思考过程(Thinking):
我们继续,本次输出创建型模式的第3讲:建造者模式,然后开始结构型模式的第一讲:代理模式。保持通俗易懂,满足面试需求。
A:
🧱 第一部分:创建型模式(第3讲)
第3讲:建造者模式 —— “分步骤造一个复杂对象”
📌 一句话理解
把复杂对象的构建过程拆成多个小步骤,可以按不同顺序或配置组装,最终得到不同表现的对象。
🎯 真实前端场景
- 复杂配置对象的生成:比如构造一个报表请求参数,有分页、排序、筛选、统计等多个可选部分。
- 表单校验器链:一步步添加校验规则。
- URL 参数构建器:逐步添加 query 参数。
💻 手写一个前端建造者示例
class RequestBuilder {
constructor() {
this.url = '';
this.method = 'GET';
this.headers = {};
this.body = null;
}
setUrl(url) { this.url = url; return this; } // 返回 this 支持链式调用
setMethod(method) { this.method = method; return this; }
setHeader(key, value) { this.headers[key] = value; return this; }
setBody(body) { this.body = body; return this; }
build() {
// 可以在这里做参数校验
if (!this.url) throw new Error('URL is required');
return {
url: this.url,
method: this.method,
headers: this.headers,
body: this.body
};
}
}
// 使用
const request = new RequestBuilder()
.setUrl('/api/user')
.setMethod('POST')
.setHeader('Content-Type', 'application/json')
.setBody({ name: 'John' })
.build();❓ 面试高频追问
Q:建造者模式 vs 工厂模式的区别? A:
- 工厂模式:一把梭,直接产出完整对象,适合创建逻辑简单或固定结构的对象。
- 建造者模式:分步构建,适合对象结构复杂、参数可选、不同组合差异大的场景。
面试金句:「工厂模式像快餐店点套餐,直接拿;建造者模式像自助餐,自己一步步选。」
Q:前端真的需要建造者模式吗? A:传统面向对象语言(Java)中常见,但前端可以用对象字面量 + 默认值替代。不过当构建步骤很多且有顺序依赖时,建造者模式让代码更清晰。Vue 源码中的 config 合并策略也可看作一种变体。
🧰 第二部分:结构型模式(第1讲)
第1讲:代理模式 —— “找个中间人帮你做事”
📌 一句话理解
提供一个替身或占位符来控制对原对象的访问,可以在访问前后做额外操作(比如缓存、校验、日志、懒加载)。
🎯 真实前端场景(大厂必考)
- Vue3 响应式系统:用
Proxy代理对象,拦截 get/set 实现依赖收集和视图更新。 - 图片懒加载:先用一个占位图代理真实图片,滚动到可视区再加载。
- 缓存代理:缓存计算结果,避免重复请求或计算。
- 权限校验代理:在发送请求前检查是否有 token。
💻 手写三种常见代理
1. 缓存代理(面试手写高频)
// 原函数:计算斐波那契(耗时)
function fib(n) {
if (n < 2) return n;
return fib(n - 1) + fib(n - 2);
}
// 代理:加缓存
const fibProxy = (function() {
const cache = {};
return function(n) {
if (cache[n] !== undefined) return cache[n];
cache[n] = fib(n);
return cache[n];
}
})();2. Vue3 响应式代理(简化版)
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
console.log(`读取 ${key}`);
return target[key];
},
set(target, key, value) {
console.log(`设置 ${key} = ${value}`);
target[key] = value;
// 触发更新(实际调用 effect)
return true;
}
});
}3. 图片懒加载代理
<img data-src="real.jpg" src="placeholder.jpg" />const imgProxy = {
loadRealImg(imgElement) {
const realSrc = imgElement.dataset.src;
const img = new Image();
img.onload = () => { imgElement.src = realSrc; };
img.src = realSrc;
}
};❓ 面试高频追问
Q:代理模式 vs 装饰器模式的区别? A:这是大厂常问的辨析题。
- 代理模式:控制访问,强调对目标对象的访问管理(缓存、校验、懒加载),接口通常不变。
- 装饰器模式:动态添加功能,强调增强行为(加日志、加格式转换),会一层层包装。
举例:代理像私人助理帮你过滤电话;装饰器像手机壳,套一层加个支架。
Q:Proxy 和 Object.defineProperty 实现响应式的区别? A:
defineProperty:只能监听已有属性,需要递归遍历,无法监听数组索引和 length 变化。Proxy:监听整个对象,包括新增属性和数组变化,性能更好,代码更简洁。
🎯 本阶段复习要点
| 模式 | 核心记忆词 | 手写场景 |
|---|---|---|
| 单例模式 | 全局唯一 | 弹窗、Store |
| 工厂模式 | 集中创建 | 根据角色生成组件 |
| 建造者模式 | 分步构建 | 复杂参数对象 |
| 代理模式 | 中间人控制 | 缓存、Proxy 响应式、懒加载 |
下一次我会继续讲结构型模式中的装饰器模式和适配器模式(这两者常和代理模式一起比较)。你想继续听吗?还是想先针对已讲的内容做模拟面试追问?
Q4:
继续
思考过程(Thinking):
我们继续讲结构型模式,接下来讲装饰器模式和适配器模式,然后对比三者。注意语言通俗,面试重点。
A:
🧰 第二部分:结构型模式(第2讲)
第2讲:装饰器模式 —— “套娃式增强功能”
📌 一句话理解
不改变原对象,把它包一层,动态添加新功能。就像给手机套壳——手机还是那个手机,但多了防摔功能。
🎯 真实前端场景(高频)
- React 高阶组件(HOC):
withLogger(Component)、withAuth(Component) - 日志装饰器:给函数自动打印执行时间
- 权限装饰:给按钮包裹一层权限校验逻辑
- TypeScript / Java 的
@Decorator语法(ES7 提案)
💻 手写示例
1. 函数装饰器(日志)
// 原函数
function fetchUser(id) {
return `用户${id}`;
}
// 装饰器:加日志
function logDecorator(fn) {
return function(...args) {
console.log(`调用 ${fn.name},参数:`, args);
const result = fn.apply(this, args);
console.log(`返回值:`, result);
return result;
}
}
const wrappedFetch = logDecorator(fetchUser);
wrappedFetch(123);2. React HOC 装饰器风格
function withLogger(WrappedComponent) {
return function(props) {
console.log('渲染组件:', WrappedComponent.name);
return <WrappedComponent {...props} />;
}
}
// 使用装饰器语法(需配置)
@withLogger
class MyComponent extends React.Component {}3. 类属性装饰器(拦截 setter)
function readonly(target, key, descriptor) {
descriptor.writable = false;
return descriptor;
}
class User {
@readonly
name = '张三';
}
// 尝试修改 user.name = '李四' 会静默失败或报错❓ 面试高频追问
Q:装饰器模式 vs 代理模式的区别(重难点) A:两者结构很像,但意图不同:
| 对比点 | 装饰器模式 | 代理模式 |
|---|---|---|
| 核心目的 | 动态添加功能(增强) | 控制访问(管理) |
| 关注点 | 给对象增加行为 | 限制、缓存、懒加载 |
| 关系 | 装饰器和原对象实现同一接口,层层包装 | 代理持有原对象的引用,代为调用 |
| 典型场景 | 日志、权限、格式化 | 缓存、虚拟代理、保护代理 |
面试金句:「装饰器模式像一个俄罗斯套娃,每套一层加一个新能力;代理模式像私人助理,控制你什么时候能见到老板。」
Q:前端常用装饰器吗? A:React HOC 就是装饰器思想的体现。ES7 装饰器语法在 Angular/Nest.js 中大量使用,但 React 官方更推荐 Hooks 替代 HOC。面试可以说:「虽然原生装饰器还没完全标准化,但 HOC 和高阶函数已经在日常开发中实践了这个思想。」
第3讲:适配器模式 —— “换个插头继续用”
📌 一句话理解
把接口不兼容的两个东西对接起来,就像出国旅行用的电源转换插头。
🎯 真实前端场景(实用高频)
- 适配不同 API 响应格式:后端返回
{ user_name: 'xxx' },前端需要{ userName: 'xxx' } - 封装第三方库:换库时只改适配器,业务代码无感知
- 兼容旧接口:老代码用
getTitle(),新组件用getName(),写个适配器
💻 手写示例
1. API 响应适配器
// 后端原始响应
const oldResponse = { user_name: '张三', user_age: 18 };
// 适配器
function userAdapter(response) {
return {
name: response.user_name,
age: response.user_age
};
}
// 业务代码使用适配后的数据
const newUser = userAdapter(oldResponse);
console.log(newUser.name); // '张三'2. 第三方地图库适配
// 假设想把 Google Maps API 换成 百度地图 API
class GoogleMap {
show(lat, lng) { console.log(`Google 显示 ${lat},${lng}`); }
}
class BaiduMap {
display(location) { console.log(`百度 显示 ${location.lat},${location.lng}`); }
}
// 适配器:让百度地图符合 Google 接口
class BaiduMapAdapter {
constructor() { this.map = new BaiduMap(); }
show(lat, lng) {
this.map.display({ lat, lng });
}
}
// 业务代码统一调用 show
function renderMap(mapAdapter) {
mapAdapter.show(39.9, 116.4);
}
renderMap(new BaiduMapAdapter()); // 输出:百度 显示 39.9,116.4❓ 面试高频追问
Q:适配器模式 vs 装饰器模式 vs 代理模式? A:这是结构型模式三兄弟辨析题。
| 模式 | 一句话 | 改变接口? | 主要目的 |
|---|---|---|---|
| 适配器 | 转换接口,让不兼容的能一起工作 | ✅ 会改变接口 | 兼容 |
| 装饰器 | 不改变接口,添加新功能 | ❌ 保持接口一致 | 增强 |
| 代理 | 控制访问,接口通常不变 | ❌ 通常不变 | 管控 |
例子:
- 适配器:英式插头 → 中式插座(接口变了)
- 装饰器:普通手机 → 加个防摔壳(接口没变,还是能打电话)
- 代理:明星的经纪人(接口没变,但帮你过滤粉丝)
Q:前端适配器实战举例? A:Vue 的 computed 本质上也是一种适配器——把原始数据转换成视图需要的格式;还有 axios 的拦截器可以统一适配响应数据。
🎯 结构型模式阶段总结
| 模式 | 核心关键词 | 前端高频场景 | 手写重点 |
|---|---|---|---|
| 代理模式 | 控制访问 | Vue Proxy、懒加载、缓存 | 缓存代理、Proxy 拦截 |
| 装饰器模式 | 动态增强 | HOC、日志装饰器 | 高阶函数包装 |
| 适配器模式 | 接口转换 | API 适配、第三方库兼容 | 转换函数、类适配 |
面试建议:
- 被问到任意一个,先一句话说清楚「它解决什么问题」
- 然后举例说明(Vue/React 中的体现)
- 如果被追问区别,直接拿出上面的对比表格回答
📌 下一步预告
下一次我们进入行为型模式,先讲观察者模式 vs 发布-订阅模式(大厂绝对高频,尤其是 Vue 响应式原理),再讲策略模式(消灭 if-else 神器)。需要我继续吗?
Q5:
继续
思考过程(Thinking):
我们继续行为型模式。用户要求分多次输出,之前已经讲了创建型(单例、工厂、建造者)和结构型(代理、装饰器、适配器)。接下来行为型模式,先讲观察者模式与发布-订阅模式(高频辨析),以及策略模式。注意保持通俗易懂、面试层次。
A:
⚙️ 第三部分:行为型模式(第1讲)
第1讲:观察者模式 vs 发布-订阅模式 —— “你喊一声,我响应”
这是面试中的绝对高频考点,尤其是结合 Vue 响应式原理来问。
📌 一句话理解
- 观察者模式:目标对象(Subject)直接通知所有观察者(Observer),大家互相认识。
- 发布-订阅模式:发布者(Publisher)和订阅者(Subscriber)互不认识,通过事件中心(Event Bus) 通信。
🎯 真实前端场景
| 模式 | 场景举例 |
|---|---|
| 观察者模式 | Vue 响应式系统(Dep 通知 Watcher)、DOM 事件监听(addEventListener) |
| 发布-订阅模式 | EventBus、Vue 的 $on/$emit、Node.js 的 EventEmitter、消息队列 |
💻 手写实现(面试必会)
1. 观察者模式
// 被观察者(目标)
class Subject {
constructor() {
this.observers = [];
}
addObserver(observer) {
this.observers.push(observer);
}
notify(data) {
this.observers.forEach(observer => observer.update(data));
}
}
// 观察者
class Observer {
constructor(name) {
this.name = name;
}
update(data) {
console.log(`${this.name} 收到通知:`, data);
}
}
// 使用
const sub = new Subject();
const obs1 = new Observer('观察者A');
const obs2 = new Observer('观察者B');
sub.addObserver(obs1);
sub.addObserver(obs2);
sub.notify('下雨了');2. 发布-订阅模式(事件中心)
class EventBus {
constructor() {
this.events = {};
}
on(eventName, callback) {
if (!this.events[eventName]) this.events[eventName] = [];
this.events[eventName].push(callback);
}
emit(eventName, data) {
const callbacks = this.events[eventName];
if (callbacks) callbacks.forEach(cb => cb(data));
}
off(eventName, callback) {
if (!callback) delete this.events[eventName];
else {
const idx = this.events[eventName]?.indexOf(callback);
if (idx !== -1) this.events[eventName].splice(idx, 1);
}
}
}
// 使用
const bus = new EventBus();
bus.on('login', (user) => console.log(`欢迎 ${user}`));
bus.emit('login', '张三');❓ 面试高频追问(重要!)
Q1:两者的核心区别是什么?
| 对比点 | 观察者模式 | 发布-订阅模式 |
|---|---|---|
| 耦合度 | 目标和观察者互相知道,松耦合但仍有依赖 | 完全解耦,通过事件中心中介 |
| 角色 | Subject + Observer | Publisher + Event Channel + Subscriber |
| 灵活性 | 同步通知,通常按顺序执行 | 支持异步、消息持久化、更灵活 |
| 典型实现 | Vue 响应式、RxJS 的 Observable | EventBus、RabbitMQ、浏览器事件 |
面试金句:「观察者模式是‘面对面’通知,发布-订阅是‘对讲机’广播。」
Q2:Vue 响应式用的是观察者还是发布-订阅? A:两者都有。Vue 的响应式核心是观察者模式:Dep(目标)收集 Watcher(观察者),数据变化时 Dep 通知 Watcher 更新。但组件通信的 $on/$emit 是发布-订阅模式。
Q3:为什么 Vue 不直接用发布-订阅做响应式? A:因为响应式需要精确的依赖收集(哪个组件用到了这个数据就通知谁),观察者模式更直接高效;发布-订阅适合跨组件、跨模块的松散通信。
第2讲:策略模式 —— “消灭 if-else 的神器”
📌 一句话理解
定义一族算法,把它们封装起来,让它们可以互相替换。调用方根据需要选择策略,而不是写死逻辑。
🎯 真实前端场景
- 表单验证:不同字段用不同验证规则(非空、手机号、邮箱…)
- 年终奖计算:不同绩效等级用不同系数
- 动画缓动函数:linear、ease-in、ease-out
- 支付方式:微信、支付宝、银行卡
💻 手写示例
传统 if-else(坏味道)
function getSalary(level, baseSalary) {
if (level === 'S') return baseSalary * 3;
else if (level === 'A') return baseSalary * 2;
else if (level === 'B') return baseSalary * 1.5;
else return baseSalary;
}策略模式重构
// 策略对象
const salaryStrategies = {
S: (base) => base * 3,
A: (base) => base * 2,
B: (base) => base * 1.5,
C: (base) => base
};
function getSalary(level, baseSalary) {
const strategy = salaryStrategies[level];
return strategy ? strategy(baseSalary) : baseSalary;
}
// 甚至可以动态添加策略
salaryStrategies['SSS'] = (base) => base * 5;表单验证例子
const validators = {
isNotEmpty: (val) => val.trim() !== '',
isEmail: (val) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val),
minLength: (len) => (val) => val.length >= len
};
function validate(value, rules) {
for (let rule of rules) {
let result;
if (typeof rule === 'function') result = rule(value);
else if (rule.name) result = validators[rule.name](value);
if (!result) return false;
}
return true;
}
// 使用
validate('abc', [validators.isNotEmpty, validators.minLength(3)]); // true❓ 面试高频追问
Q:策略模式解决了什么问题? A:消除大量 if-else / switch,符合开闭原则(新增策略不改原有代码),提高可测试性和可维护性。
Q:策略模式和工厂模式的区别? A:
- 工厂模式:创建什么对象(创建型)
- 策略模式:怎么执行某个行为(行为型)
举例:工厂模式决定「用微信支付还是支付宝支付」;策略模式决定「计算折扣时用满减策略还是打折策略」。
Q:前端框架中哪里用到了策略模式? A:Vue 的 options 合并策略、Axios 的适配器(浏览器 vs Node)、各种校验库(async-validator)。
🎯 本阶段小结
| 模式 | 核心记忆词 | 手写重点 |
|---|---|---|
| 观察者模式 | 目标通知观察者 | Subject + Observer 类 |
| 发布-订阅模式 | 事件中心解耦 | EventBus (on/emit/off) |
| 策略模式 | 封装算法可互换 | 策略对象 + 调用方选择 |
面试建议:
- 观察者和发布-订阅的区别是必考辨析题,务必能用表格或例子讲清楚
- 策略模式展示代码时,强调「消除 if-else」和「可扩展性」
📌 下一步预告
下一次继续行为型模式:模板方法模式(框架生命周期)和责任链模式(请求处理链)。需要继续吗?
Q6:
继续
思考过程(Thinking):
我们继续行为型模式的讲解。接下来讲模板方法模式和责任链模式。保持风格一致,通俗易懂,附带面试高频追问。
A:
⚙️ 第三部分:行为型模式(第2讲)
第3讲:模板方法模式 —— “我定流程,你来填空”
📌 一句话理解
在父类中定义一个算法的骨架(模板),把一些具体步骤延迟到子类中实现。子类可以不改变算法结构,只重写某些步骤。
🎯 真实前端场景
- 框架生命周期钩子:Vue/React 的
mounted、updated等,框架定好流程,你在钩子里填逻辑。 - 通用请求流程:前置拦截 → 发请求 → 后置处理 → 错误处理,子类可重写拦截或错误处理。
- 组件渲染流程:
beforeRender → render → afterRender,不同组件实现自己的渲染细节。
💻 手写示例
1. 通用请求模板
// 抽象模板(父类)
class RequestTemplate {
// 模板方法:定义请求流程骨架
async request(url, options) {
const config = this.beforeRequest(options);
try {
const data = await this.doRequest(url, config);
return this.afterRequest(data);
} catch (error) {
return this.handleError(error);
}
}
// 钩子方法(子类可重写)
beforeRequest(options) { return options; }
afterRequest(data) { return data; }
handleError(error) { throw error; }
// 必须由子类实现的方法
doRequest(url, options) {
throw new Error('子类必须实现 doRequest');
}
}
// 具体子类:JSON 请求
class JSONRequest extends RequestTemplate {
beforeRequest(options) {
return {
...options,
headers: { 'Content-Type': 'application/json', ...options.headers }
};
}
async doRequest(url, options) {
const res = await fetch(url, options);
return res.json();
}
handleError(error) {
console.error('JSON请求失败:', error);
return null;
}
}2. Vue 生命周期类比
// 框架内部伪代码
class VueComponent {
_mount() { // 模板方法
this.beforeCreate();
this.createData();
this.beforeMount();
this.render();
this.mounted();
}
beforeCreate() {}
createData() { /* 实现 */ }
beforeMount() {}
render() { throw new Error('must implement') }
mounted() {}
}
// 开发者写组件时只需重写钩子❓ 面试高频追问
Q:模板方法模式的核心思想是什么? A:好莱坞原则——「Don't call us, we'll call you」。框架控制流程,调用你提供的钩子。这能实现复用不变部分,定制变化部分。
Q:和策略模式的区别? A:
- 模板方法:在继承层次中,父类定骨架,子类填空(横向扩展)
- 策略模式:通过组合,把算法抽离成独立策略对象(纵向扩展)
举例:
- 模板方法:做菜流程固定(洗→切→炒→装盘),但你可以决定放什么调料。
- 策略模式:今天选「川菜策略」还是「粤菜策略」,整体替换一套做法。
Q:前端框架中哪里体现了模板方法? A:Vue 的 options 合并策略、生命周期钩子;React 类组件的生命周期方法;Web Component 的 connectedCallback 等。本质上框架都遵循「骨架 + 钩子」的模式。
第4讲:责任链模式 —— “层层上报,直到有人处理”
📌 一句话理解
将请求沿着一条链传递,链上的每个处理器决定处理还是转发,直到某个处理器处理掉它。
🎯 真实前端场景
- 中间件机制:Express / Koa 的
app.use(),请求一层层经过中间件。 - 表单校验:非空 → 格式校验 → 长度校验 → 自定义规则,任何一个失败就拦截。
- 权限审批流:员工请假 → 组长 → 经理 → HR。
- Axios 拦截器:请求拦截器链和响应拦截器链。
💻 手写示例
1. 简单责任链(校验器)
// 处理器基类
class ValidatorHandler {
setNext(handler) {
this.next = handler;
return handler; // 方便链式调用
}
handle(request) {
if (this.next) return this.next.handle(request);
return { success: true };
}
}
// 具体处理器
class NotEmptyValidator extends ValidatorHandler {
handle(request) {
if (!request.value || request.value.trim() === '') {
return { success: false, msg: '不能为空' };
}
return super.handle(request);
}
}
class MinLengthValidator extends ValidatorHandler {
constructor(min) { super(); this.min = min; }
handle(request) {
if (request.value.length < this.min) {
return { success: false, msg: `长度不能小于${this.min}` };
}
return super.handle(request);
}
}
// 使用
const notEmpty = new NotEmptyValidator();
const minLength = new MinLengthValidator(3);
notEmpty.setNext(minLength);
console.log(notEmpty.handle({ value: 'ab' })); // { success: false, msg: '长度不能小于3' }
console.log(notEmpty.handle({ value: 'abc' })); // { success: true }2. 模拟 Koa 中间件(洋葱模型简化版)
class MiddlewareChain {
constructor() { this.middlewares = []; }
use(fn) { this.middlewares.push(fn); return this; }
async run(ctx) {
let index = -1;
const dispatch = async (i) => {
if (i <= index) throw new Error('next() called multiple times');
index = i;
const fn = this.middlewares[i];
if (!fn) return;
await fn(ctx, () => dispatch(i + 1));
};
await dispatch(0);
return ctx;
}
}
// 使用
const chain = new MiddlewareChain();
chain.use(async (ctx, next) => {
console.log('日志开始');
await next();
console.log('日志结束');
});
chain.use(async (ctx, next) => {
ctx.body = 'Hello';
await next();
});
chain.run({}).then(console.log);
// 输出:日志开始 → (无更多中间件) → 日志结束 → { body: 'Hello' }❓ 面试高频追问
Q:责任链模式有什么优缺点? A:
- ✅ 优点:解耦发送者和接收者;可以动态组合链;增加新处理器方便。
- ❌ 缺点:请求可能无人处理;调试时链路过长不易追踪;性能有损耗。
Q:责任链模式和装饰器模式的区别? A:
- 责任链:每个节点选择处理或传递,通常只被一个节点处理。
- 装饰器:每个装饰器都会执行,层层增强。
例子:
- 责任链:请假申请,组长批了就不再到经理。
- 装饰器:给蛋糕加奶油、加水果、加巧克力,所有装饰都生效。
Q:前端哪些库用了责任链? A:Express/Koa 中间件、Axios 拦截器、各种表单校验库、Promise 的 then/catch 链(也可以看作责任链的变体)。
🎯 行为型模式阶段总结(已讲)
| 模式 | 核心记忆词 | 前端高频场景 |
|---|---|---|
| 观察者模式 | 直接通知 | Vue 响应式 (Dep-Watcher) |
| 发布-订阅模式 | 事件中心 | EventBus、$on/$emit |
| 策略模式 | 消灭 if-else | 表单验证、折扣计算 |
| 模板方法模式 | 骨架 + 钩子 | 生命周期、请求流程 |
| 责任链模式 | 层层传递 | 中间件、拦截器、审批流 |
📌 下一步预告
下一讲是行为型模式的最后部分:迭代器模式(遍历集合的统一接口)和状态模式(状态驱动行为)。需要继续吗?
Q7:
继续
思考过程(Thinking):
我们继续行为型模式的最后部分,以及整个设计模式总结。需要给出迭代器模式和状态模式,然后做一个完整的分类汇总和面试策略。
A:
⚙️ 第三部分:行为型模式(第3讲)
第5讲:迭代器模式 —— “只管遍历,不管内部”
📌 一句话理解
提供一种统一的方法来顺序访问一个集合对象的内部元素,而不需要暴露该集合的内部结构。
🎯 真实前端场景
- JavaScript 原生迭代器:
for...of、Array.prototype.map/forEach、Set/Map的遍历 - 手写迭代器:自定义数据结构需要被遍历时(如树、链表、图)
- 分页遍历:后端分页数据,封装成可迭代的接口
💻 手写示例
1. 原生迭代器协议
// 任何实现了 Symbol.iterator 方法的对象都可以被 for...of 遍历
const myCollection = {
items: [10, 20, 30],
[Symbol.iterator]() {
let index = 0;
const items = this.items;
return {
next() {
if (index < items.length) {
return { value: items[index++], done: false };
} else {
return { done: true };
}
}
};
}
};
for (let val of myCollection) {
console.log(val); // 10 20 30
}2. 树结构的迭代器(深度优先)
class TreeNode {
constructor(value, children = []) {
this.value = value;
this.children = children;
}
// 实现迭代器:深度优先遍历
[Symbol.iterator]() {
const stack = [this];
return {
next() {
while (stack.length) {
const node = stack.pop();
// 子节点逆序推入,保证正序遍历顺序
for (let i = node.children.length - 1; i >= 0; i--) {
stack.push(node.children[i]);
}
return { value: node.value, done: false };
}
return { done: true };
}
};
}
}
const tree = new TreeNode(1, [
new TreeNode(2, [new TreeNode(4), new TreeNode(5)]),
new TreeNode(3)
]);
for (let val of tree) console.log(val); // 1 2 4 5 33. 分页迭代器(包装后端分页API)
class PageIterator {
constructor(fetchPage) { // fetchPage 接收页码,返回 Promise<{data, hasMore}>
this.fetchPage = fetchPage;
this.currentPage = 1;
this.hasMore = true;
}
async next() {
if (!this.hasMore) return { done: true };
const { data, hasMore } = await this.fetchPage(this.currentPage);
this.hasMore = hasMore;
this.currentPage++;
return { value: data, done: false };
}
// 使其可异步迭代
[Symbol.asyncIterator]() {
return this;
}
}
// 使用
const iter = new PageIterator((page) => fetch(`/api/list?page=${page}`).then(r => r.json()));
for await (let pageData of iter) {
console.log(pageData);
}❓ 面试高频追问
Q:迭代器模式解决了什么问题? A:解耦遍历算法和数据结构。不管数据是数组、树还是数据库游标,调用方都用统一的 next() 或 for...of 来遍历。
Q:迭代器模式和 Generator 函数的关系? A:Generator 函数返回的对象天然实现了迭代器协议,是写迭代器的语法糖。
function* range(start, end) {
for (let i = start; i <= end; i++) yield i;
}
for (let num of range(1, 5)) console.log(num);第6讲:状态模式 —— “状态变了,行为就变”
📌 一句话理解
允许一个对象在其内部状态改变时改变它的行为,看起来就像修改了它的类。
🎯 真实前端场景
- 有限状态机:Promise(pending → fulfilled/rejected)、红绿灯、电梯状态
- 组件状态管理:加载中 → 成功 → 失败 → 重试
- 游戏角色状态:站立、跑动、跳跃、倒地,每个状态有不同的行为逻辑
- XState 库:专门用于状态机
💻 手写示例
1. 传统 if-else 写法(坏味道)
class TrafficLight {
constructor() { this.state = 'red'; }
change() {
if (this.state === 'red') {
console.log('红灯 → 绿灯');
this.state = 'green';
} else if (this.state === 'green') {
console.log('绿灯 → 黄灯');
this.state = 'yellow';
} else if (this.state === 'yellow') {
console.log('黄灯 → 红灯');
this.state = 'red';
}
}
}2. 状态模式重构
// 状态接口(抽象)
class LightState {
constructor(light) { this.light = light; }
change() { throw new Error('子类实现'); }
}
// 具体状态类
class RedState extends LightState {
change() {
console.log('红灯 → 绿灯');
this.light.setState(this.light.greenState);
}
}
class GreenState extends LightState {
change() {
console.log('绿灯 → 黄灯');
this.light.setState(this.light.yellowState);
}
}
class YellowState extends LightState {
change() {
console.log('黄灯 → 红灯');
this.light.setState(this.light.redState);
}
}
// 上下文(维护状态)
class TrafficLight {
constructor() {
this.redState = new RedState(this);
this.greenState = new GreenState(this);
this.yellowState = new YellowState(this);
this.state = this.redState;
}
setState(state) { this.state = state; }
change() { this.state.change(); }
}
// 使用
const light = new TrafficLight();
light.change(); // 红灯→绿灯
light.change(); // 绿灯→黄灯
light.change(); // 黄灯→红灯3. 更简洁的实现(函数式 + 策略表)
const stateMachine = {
state: 'red',
transitions: {
red: { change: () => { console.log('→绿'); return 'green'; } },
green: { change: () => { console.log('→黄'); return 'yellow'; } },
yellow: { change: () => { console.log('→红'); return 'red'; } }
},
change() {
const transition = this.transitions[this.state];
if (transition && transition.change) {
this.state = transition.change();
}
return this;
}
};
stateMachine.change().change().change();❓ 面试高频追问
Q:状态模式和策略模式有什么区别? A:两者结构相似,但意图不同:
| 对比点 | 状态模式 | 策略模式 |
|---|---|---|
| 核心 | 状态变化导致行为变化 | 算法可互换,但对象状态不变 |
| 状态切换 | 状态通常由上下文或状态自己决定 | 策略由调用方主动选择 |
| 关注点 | 对象在不同状态下不同行为 | 同一任务的不同算法实现 |
例子:
- 状态模式:订单状态(待支付→已支付→已发货→已完成),每个状态下取消订单的行为不同。
- 策略模式:支付方式(微信、支付宝、银行卡),算法可替换但订单状态不变。
Q:前端状态管理(Vuex/Redux)和状态模式的关系? A:状态模式是设计层面对象内部状态机;Vuex/Redux 是全局状态管理,但其中的 reducer 本质上也是一个状态机(根据 action type 和当前 state 返回新 state)。
Q:什么场景该用状态模式? A:当一个对象的行为严重依赖于它的状态,并且状态数量较多、状态转换频繁时。比如表单的多步骤流程、播放器的播放/暂停/停止状态。
📊 完整设计模式分类总结(面试速记版)
创建型(5种)
| 模式 | 一句话记忆 |
|---|---|
| 单例 | 全局唯一,一个实例到处用 |
| 工厂 | 集中创建,类型变了不用改调用方 |
| 抽象工厂 | 生产一套产品(如 UI 主题) |
| 建造者 | 分步构造复杂对象,链式调用 |
| 原型 | 克隆自己,JS 天生支持 Object.create |
结构型(7种)
| 模式 | 一句话记忆 |
|---|---|
| 适配器 | 转换接口,让不兼容的能合作 |
| 装饰器 | 套娃增强,不改原对象加功能 |
| 代理 | 找个中间人,控制访问(缓存/懒加载) |
| 外观 | 封装复杂子系统,提供简单入口 |
| 桥接 | 抽象与实现分离,各自独立变化 |
| 组合 | 树形结构,个体与整体统一对待 |
| 享元 | 共享细粒度对象,节省内存 |
行为型(11种)
| 模式 | 一句话记忆 |
|---|---|
| 观察者 | 目标直接通知观察者(Vue 响应式) |
| 发布-订阅 | 通过事件中心解耦(EventBus) |
| 策略 | 算法可互换,消灭 if-else |
| 模板方法 | 骨架固定,钩子可变(生命周期) |
| 责任链 | 层层传递,直到有人处理(中间件) |
| 迭代器 | 统一遍历接口,不暴露内部结构 |
| 状态 | 状态变了行为就变(状态机) |
| 命令 | 请求封装成对象,可排队/撤销 |
| 备忘录 | 保存快照,支持恢复(时间旅行) |
| 中介者 | 对象之间通过中介通信,降低网状耦合 |
| 访问者 | 数据结构稳定,算法易扩展 |
面试建议:不需要背全 23 种,重点掌握 加粗 的前 10 种,其他知道概念即可。
🎯 最终面试应答策略回顾
被问到任意模式时,按三步回答:
- 一句话定义(解决什么问题)
- 前端真实场景(Vue/React 项目哪里用过)
- 手写核心代码(简化的关键片段)
被问到「模式对比」时(如观察者 vs 发布-订阅、代理 vs 装饰器):
- 先说核心区别(意图不同)
- 再说结构差异(耦合度、接口是否改变)
- 最后举例场景辅助说明
被问到「设计原则」时:
- 重点提 SOLID 中的单一职责、开闭原则、依赖倒置
- 结合具体模式举例,如:策略模式符合开闭原则(新增策略不改旧代码)
被问到「模式带来的问题」时:
- 承认模式会增加代码复杂度、类数量
- 强调要根据场景权衡,不要滥用
📚 最后的小贴士
- 不要背诵定义,要讲清楚「什么时候用」和「为什么用」
- 手写代码是硬门槛,每个高频模式至少能手写 10 行内的示例
- 结合框架(Vue/React)讲模式,会比单纯讲理论更让面试官认可
- 准备一个自己的项目实例,用 1-2 个模式重构过,面试时直接拿出来讲
祝面试顺利!如果还有某个模式想深入或需要模拟追问,随时告诉我。
Q8:
再将单例、工厂、观察者、策略四个高频模式串一下记忆点,核心点,注意点等等
思考过程(Thinking):
我们要求对四个高频模式进行串讲:单例、工厂、观察者、策略。需要给出记忆点、核心点、注意点等。输出要简洁清晰,适合面试前速记。
A:
🔥 四大高频设计模式「串烧记忆卡」
适合面试前 10 分钟快速回顾,每个模式用 一句话核心 + 记忆锚点 + 手写骨架 + 注意点 串起来。
1️⃣ 单例模式(Singleton)
- 一句话核心:保证一个类只有一个实例,并提供全局访问点。
- 记忆锚点:全局弹窗、Vuex Store、登录态管理。
- 手写骨架(闭包懒汉):javascript
const Singleton = (function() { let instance = null; return { getInstance: () => instance || (instance = {}) }; })(); - ES6 模块版:直接 export 一个对象,模块系统天然单例。
- 注意点:
- 不要滥用 —— 全局状态会增加隐式耦合,测试困难。
- 前端几乎没有“饿汉模式”(页面加载时就创建),因为不一定用到。
- 多例场景(如需要多个独立弹窗)不要强行用单例。
2️⃣ 工厂模式(Factory)
- 一句话核心:用一个工厂函数来创建对象,调用方不用自己
new,根据类型产出不同实例。 - 记忆锚点:根据权限生成不同按钮、Axios 实例工厂、消息提示(success/warning/error)。
- 手写骨架(简单工厂):javascript
class ButtonFactory { create(role) { switch(role) { case 'admin': return new AdminButton(); case 'user': return new UserButton(); default: return new GuestButton(); } } } - 注意点:
- 简单工厂适合产品类型少的场景;类型多时考虑工厂方法(每种产品一个工厂子类)。
- 工厂模式的核心价值是隔离创建逻辑,增加新产品时调用方无感知。
- 不要为了用工厂而用 —— 如果
new一个对象很简单且不会变化,直接new即可。
3️⃣ 观察者模式(Observer)
- 一句话核心:目标对象(Subject) 维护一组观察者(Observer),状态变化时直接通知所有观察者。
- 记忆锚点:Vue 响应式系统(Dep → Watcher)、DOM 事件监听。
- 手写骨架:javascript
class Subject { constructor() { this.obs = []; } add(o) { this.obs.push(o); } notify(data) { this.obs.forEach(o => o.update(data)); } } class Observer { update(data) { console.log(data); } } - 注意点:
- 与发布-订阅模式的区分:观察者模式中目标和观察者互相知道(耦合),发布-订阅通过事件中心完全解耦。
- 面试高频追问:Vue 响应式是观察者模式,
$on/$emit是发布-订阅。 - 注意内存泄漏 —— 观察者没有被正确移除会导致无法释放。
4️⃣ 策略模式(Strategy)
- 一句话核心:定义一组可互换的算法,把它们封装成独立策略,调用方根据需要选择策略。
- 记忆锚点:表单验证(不同字段不同规则)、年终奖计算、动画缓动函数。
- 手写骨架:javascript
const strategies = { S: salary => salary * 3, A: salary => salary * 2, B: salary => salary * 1.5 }; function getBonus(level, salary) { return strategies[level]?.(salary) ?? salary; } - 注意点:
- 核心价值是消灭 if-else / switch,符合开闭原则。
- 策略模式通常与简单工厂结合使用(工厂负责创建哪个策略,策略负责执行算法)。
- 不要过度设计 —— 如果只有 1-2 个简单条件,用 if-else 更直观。
📌 横向对比表(面试用来串讲)
| 模式 | 类型 | 解决什么问题 | 一句话类比 |
|---|---|---|---|
| 单例 | 创建型 | 全局唯一实例 | 公司只有一个 CEO |
| 工厂 | 创建型 | 统一创建对象,隐藏 new | 奶茶店根据你点的口味做饮料 |
| 观察者 | 行为型 | 状态变化时自动通知依赖方 | 你关注了博主,他发视频你会收到通知 |
| 策略 | 行为型 | 算法可互换,避免条件分支 | 地图 App 让你选:驾车/公交/步行策略 |
🧠 记忆口诀(助记)
单例唯一工厂造,观察通知策略算
- 单例:唯一实例
- 工厂:创建对象
- 观察:状态通知
- 策略:算法互换
⚠️ 面试常见踩坑点(避雷区)
- 单例:不要说“用
export default {}就是单例” —— 是的,但那是模块特性,不是模式本身;最好同时会写闭包版。 - 工厂:不要把工厂写成巨大的 if-else 怪物 —— 那是“简单工厂”的缺点,可以提“工厂方法”改进。
- 观察者:不要和发布-订阅混为一谈 —— 必须能说出是否通过事件中心这个核心区别。
- 策略:不要只写策略对象,忘了展示如何消除 if-else —— 代码里要对比重构前后的差异。
✅ 速查清单(面试前自问):
- 我能手写单例(闭包/模块两种方式)吗?
- 我能举例前端中的工厂模式场景吗?
- 我能说清观察者 vs 发布-订阅的区别吗?
- 我能手写一个策略模式替换 if-else 吗?
搞定这四个,大厂设计模式面试题基本能覆盖 70% 以上。