Skip to main content
我一开始把设计模式当成要背的名单,后来在项目里反复改同一段 if-else,才慢慢知道什么时候该用哪个。

Q1: 设计模式的历史?为什么前端也需要它?

1

1994 年: GoF 经典著作诞生

Erich Gamma、Richard Helm、Ralph Johnson、John Vlissides 四人(被称为”四人帮”,GoF)出版了《设计模式:可复用面向对象软件的基础》,总结了 23 种经典设计模式,分为创建型、结构型、行为型三大类。
2

前端早期: jQuery 时代的隐性应用

jQuery 的 $.ajax 本质上是工厂模式,$(document).on() 是观察者模式。前端开发者每天都在用设计模式,只是当时没有人把它们”名字化”。
3

组件化时代: 模式的显性化

React/Vue 的出现让前端从”操作 DOM”进化到”管理状态”。组件本身就是组合模式的体现,Hooks 的引入则让策略模式和装饰器模式变得无处不在。
设计模式解决的核心问题:
设计模式不是”高级语法”,而是”高级沟通方式”——它让团队用同一套词汇描述代码结构。

🚫 没有设计模式

一家医院没有科室分类。所有病人都挤在一个大厅里,内科、外科、眼科混在一起。医生想找个骨科设备,得翻遍整个大厅。每来一个新医生,都要花一个月搞清楚”东西放在哪里”。

✅ 引入设计模式

有了科室分类后,骨科设备放在骨科,眼科设备放在眼科。新来的医生一入职就知道”骨折去骨科、眼睛疼去眼科”。不是设备变好了,而是组织方式变好了。
面试时不要一上来就背诵 23 种模式的定义。挑 3-5 个你在项目中真正用过的模式,结合具体场景讲清楚”遇到了什么问题、为什么选这个模式、解决了什么”。想听的是你的决策过程,而不是背诵能力。

Q2: 工厂模式 —— 对象创建的标准化

工厂模式的核心思想是:把对象的创建过程封装起来,调用方不需要知道具体的创建细节。

简单工厂 vs 工厂方法 vs 抽象工厂

工厂模式在前端框架中的应用

React 的 createElement 就是一个工厂——它根据 type 参数创建不同的 VNode:
没有工厂模式就像你去餐厅,得自己去厨房告诉厨师”先热油、再放蒜、然后炒肉”。有了工厂模式,你只需要在菜单上写”宫保鸡丁”,厨房帮你搞定一切。你不需要知道厨师是谁、用的什么锅、火候多大。

Q3: 观察者模式 —— 对象间的通信机制

观察者模式的核心思想是:当一个对象状态变化时,自动通知所有依赖它的对象。 它是前端框架响应式系统的基础。

观察者模式 vs 发布-订阅模式

很多人会混淆这两个概念,但它们有本质区别:

Vue 响应式系统中的观察者模式

Vue 的响应式系统是观察者模式的教科书级应用:
观察者模式就像一个微信群。群主(Subject)发了一条消息,所有群成员(Observer)都会收到通知。发布-订阅模式则更像一个公众号——作者(Publisher)发布文章,读者(Subscriber)通过平台(Event Channel)看到,作者和读者互不知道对方是谁。

Q4: 单例模式 —— 全局唯一的实例

单例模式的核心思想是:保证一个类只有一个实例,并提供一个全局访问点。

单例模式在前端的应用场景

单例模式的本质是”控制实例数量”——不是语法特性,而是设计决策。

Q5: 策略模式 —— 消灭 if-else 的利器

策略模式的核心思想是:把算法(策略)封装成独立的函数或对象,让它们可以互相替换。

策略模式在表单校验中的应用

策略模式 vs 状态模式

策略模式就像地图导航——“最快路线""最短路线""避开高速”是三种策略,你(调用方)根据需求选择一种。每种策略内部的算法不同,但对外的接口一致(都是从 A 到 B)。新增”步行优先”策略?加一个新函数就行,不需要修改已有的策略代码。

Q6: 装饰器模式 —— 不修改原始代码的扩展

装饰器模式的核心思想是:在不修改原始对象的情况下,给它添加新功能。 它是 AOP(面向切面编程)的基础。

ES 装饰器语法(TC39 Stage 3)

装饰器模式 vs 继承

装饰器的叠加顺序很重要——就像穿衣服,先穿内衣后穿外套。在函数组合中,最内层的装饰器最先执行:

Q7: 代理模式 —— 控制对象的访问

代理模式的核心思想是:为一个对象提供代理,通过代理控制对原始对象的访问。 ES6 的 Proxy 就是语言层面对代理模式的原生支持。

Vue 3 响应式中的代理模式

Vue 3 用 Proxy 替代了 Vue 2 的 Object.defineProperty,这是代理模式的经典应用:

代理模式的常见变体

代理模式就像明星的经纪人。你想找明星拍广告(访问真实对象),不能直接打电话给明星,必须先联系经纪人(代理)。经纪人会帮你过滤不合理的要求(保护代理)、安排档期(调度)、甚至缓存重复的咨询(缓存代理)。明星只需要专注自己的工作,所有外部事务都由经纪人处理。

Q8: 组合模式 —— 树形结构的统一处理

组合模式的核心思想是:把对象组织成树形结构,让”单个对象”和”组合对象”具有一致的接口。 调用方不需要关心处理的是叶子节点还是组合节点。

React 组件树就是组合模式


底层源码:框架是如何应用设计模式的?

Redux 是设计模式的集大成者,它同时运用了多种经典模式:
Vue 3 的编译器大量运用了访问者模式(Visitor Pattern)来遍历和转换 AST:

十、及格回答 vs 满分回答

问题:“你在项目中用过哪些设计模式?”

及格回答(60-70分):
“我用过单例模式做全局状态管理,用过观察者模式做事件通知,用过策略模式消除 if-else。”
问题出在:背了定义但没有深度。 满分回答(90-100分):
我在项目中最常用的是策略模式和观察者模式。策略模式主要用于表单校验和支付方式选择——把每种校验规则封装成独立的策略函数,通过 Map 映射调用,新增规则只需要加一个函数,不需要修改已有的校验逻辑,符合开闭原则。[1]
观察者模式我在封装 EventBus 和处理 WebSocket 消息分发时用过。本质上就是发布-订阅的解耦——消息发送方不需要知道有多少订阅者,订阅者也可以随时取消订阅。Vue 的响应式系统底层也是这个模式,Dep 作为 Subject,Watcher 作为 Observer。[2]
我还会用装饰器模式做函数增强——比如给 API 请求函数统一添加日志、错误处理、loading 状态管理。不修改原始函数,通过组合的方式叠加功能。这在 Express/Koa 的中间件中也很常见,中间件本质上就是装饰器模式的链式应用。[3]
设计模式对我来说不是”背了 23 种然后找地方用”,而是”遇到问题时,发现这个问题别人已经解决过了,直接用现成的方案模板”。关键是理解每个模式解决的是什么问题,而不是它的类图长什么样。

常见追问

答题要点: 核心区别在于是否有”事件中心”这个中介。高分回答: “观察者模式中,Subject 直接持有 Observer 的引用,状态变化时直接调用 Observer 的 update 方法。发布-订阅模式多了一个事件中心(Event Channel),发布者和订阅者完全解耦,互不知道对方的存在。前端最常见的 EventEmitter(Node.js)和 addEventListener(浏览器 DOM)都是发布-订阅模式,而不是严格的观察者模式。因为它们都有一个中间层(事件中心)来管理注册和触发。Vue 2 的 watch 更接近观察者模式——Watcher 直接被 Dep 持有。”
答题要点: 工厂模式负责”创建”,策略模式负责”选择”。高分回答: “工厂模式解决的是’创建什么对象’的问题,策略模式解决的是’用什么算法’的问题。在实际项目中,它们经常组合使用——工厂决定创建哪个策略,策略执行具体逻辑。举个例子:支付模块中,工厂根据用户选择创建’支付宝策略’或’微信策略’,然后策略模式执行各自的支付逻辑。工厂管创建,策略管行为,职责清晰。”
答题要点: 目的不同:装饰器是”增强功能”,代理是”控制访问”。高分回答: “从结构上看,装饰器和代理很像——都是包装原始对象。但意图不同:
  • 装饰器模式:关注功能增强,可以叠加多层装饰器,每层添加一个新功能(日志、缓存、重试等)
  • 代理模式:关注访问控制,通常只有一层代理,目的是拦截、过滤或延迟访问
实际代码中,Koa 的中间件链是装饰器模式(每一层增强请求处理),ES6 的 Proxy 是代理模式(拦截属性访问)。”
答题要点: 洋葱模型 = 装饰器模式 + 组合模式。高分回答: “Koa 的中间件系统是装饰器模式的经典实现。核心是 compose 函数,它把多个中间件组合成一个洋葱模型:
每个中间件接收 ctx(上下文)和 next(下一个中间件),可以在 next() 前后添加逻辑。这就是装饰器模式的链式组合——每一层包装上一层,添加新功能。”
答题要点: 不是”选模式”,而是”识别问题”。高分回答: “选择设计模式的流程应该是:先识别问题,再匹配模式。
  1. 如果发现大量 if-else 分支 → 策略模式
  2. 如果发现对象间一对多的通知需求 → 观察者模式
  3. 如果发现需要全局唯一实例 → 单例模式
  4. 如果发现需要不修改原代码就扩展功能 → 装饰器模式
  5. 如果发现需要控制对象的访问 → 代理模式
最常见的错误是’为了用模式而用模式’。设计模式是解决特定问题的工具,不是代码风格。如果一个 if-else 只有两个分支且不会扩展,用策略模式反而是过度设计。”
答题要点: 结合具体框架源码举例。高分回答: “前端框架是设计模式的教科书:
  • React: 组合模式(组件树)、策略模式(reconciler 的 diff 策略)、工厂模式(createElement)
  • Vue: 观察者模式(响应式系统 Dep/Watcher)、代理模式(Proxy 实现 reactive)、策略模式(编译器的 transform 策略)
  • Redux: 单例模式(Store)、观察者模式(subscribe)、中间件模式(applyMiddleware = 装饰器链)
  • Express/Koa: 中间件模式(装饰器 + 组合模式的洋葱模型)”
答题要点: 过度设计比不用设计模式更危险。高分回答: “会。设计模式最大的风险就是过度设计。我的判断标准是:
  1. YAGNI 原则(You Ain’t Gonna Need It):如果当前只有 2 个分支且确定不会扩展,用 if-else 就够了,不需要策略模式
  2. 三振原则:当你第三次写类似的 if-else 时,才考虑重构为策略模式
  3. 团队认知成本:如果团队成员不熟悉某个模式,引入它反而增加维护成本
过度设计的典型症状:
  • 一个简单的工具函数被包装了 3 层装饰器
  • 只有一个实现的接口和抽象类
  • 为了’可扩展性’预留了 10 个扩展点,但 3 年来一个都没用过
设计模式应该是’被问题逼出来的’,而不是’提前设计进去的’。”
答题要点: 用递归 + Promise 实现,核心是 next 函数的传递。高分回答: “洋葱模型的核心是:每个中间件可以决定’是否继续执行下一个’,且每个中间件可以在 next() 前后都有逻辑。
这个模型的价值在于:每个中间件职责单一(日志、鉴权、缓存、错误处理),通过组合形成完整的请求处理管道。“

延伸阅读与引用参考