# 设计模式
⚡ 30 秒速记
- 设计模式 = 反复出现的设计问题的现成解法,核心目标就一个:把变化隔离开
- 按问题找分类:怎么创建对象 → 创建型;怎么组合对象 → 结构型;对象怎么协作 → 行为型
- 前端高频六个:单例、工厂、代理、策略、发布订阅、适配器
- 用对没用对看需求变化时:是新加一个实现,还是到处改旧的
if/else - 模式有成本,简单逻辑直接写;分支膨胀、模块互相牵扯时再引入
设计模式说白了就是前人总结的、针对常见设计问题的套路,核心是把容易变的部分隔离出来。 比如表单有十几种控件,渲染逻辑里写一堆 switch,每加一种控件都要改主流程,这时候用工厂加注册表,新控件只要注册一下就行。前端最常用的是单例、工厂、代理、策略、发布订阅和适配器,Vue 的响应式、Axios 拦截器、EventEmitter 里都有它们的影子。但我不会为了用模式而用模式,三个分支的事直接写清楚就好。
# 一、基础篇
这一章按「基础篇 → 创建型 → 结构型 → 行为型」的顺序展开。先过一遍基础,是因为 JavaScript 里的设计模式几乎都建立在 this、闭包、原型链和 ES6 语法之上,这几样没吃透,后面的代码看着就像在背。
三大类模式各自解决的问题可以这样记:
- 创建型:对象怎么造。单例保证只有一个,工厂把「造哪个」藏起来,抽象工厂成套地造,建造者分步骤地造。
- 结构型:对象怎么拼。代理控制访问,适配器转换接口,装饰者叠加功能,享元共享重复的部分。
- 行为型:对象之间怎么协作。策略替换算法,观察者 / 发布订阅解耦通知,迭代器统一遍历,职责链逐级处理。
前端里最容易遇到的例子:
// 策略模式:用查表替代 if/else
const rules = {
required: (v) => v !== '' || '必填',
mobile: (v) => /^1\d{10}$/.test(v) || '手机号格式不对',
}
const validate = (value, ruleNames) =>
ruleNames.map((name) => rules[name](value)).find((r) => r !== true)
console.log(validate('', ['required', 'mobile'])) // 输出: 必填
console.log(validate('123', ['required', 'mobile'])) // 输出: 手机号格式不对
要加「邮箱校验」,只需要在 rules 里加一行,validate 一个字都不用改,这就是「对扩展开放,对修改关闭」。看每个模式时都可以问自己一句:它把什么变化隔离掉了?答得上来,才算真懂。
💬 面试官追问
-
你说
Vue里有设计模式,能举个具体例子吗?Vue 3响应式用Proxy拦截读写,这是代理;依赖收集后数据一变就通知所有effect,这是观察者。Vue 2用的是Object.defineProperty,思路一样,只是代理手段不同。 -
策略模式和一堆
if/else比,好在哪?每个分支变成一个独立函数放进对象里,调用时
strategies[type](value)查表执行。加新规则只加一个函数,不动原来的判断逻辑,单个策略也好单测。 -
发布订阅和观察者模式是一回事吗?
很像但不一样。观察者是目标对象自己维护观察者列表,直接通知;发布订阅中间多了一个事件中心,发布者和订阅者互相不知道对方存在,耦合更低。
-
项目里只有三个分支,同事非要抽成工厂加注册表,你怎么看?
我会劝他先别抽。三个分支直接写一眼就能看懂,抽象之后反而要多跳两个文件。等分支真的多起来、或者要支持外部扩展时再重构,成本并不高。