# 设计模式

⚡ 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) 查表执行。加新规则只加一个函数,不动原来的判断逻辑,单个策略也好单测。

  • 发布订阅和观察者模式是一回事吗?

    很像但不一样。观察者是目标对象自己维护观察者列表,直接通知;发布订阅中间多了一个事件中心,发布者和订阅者互相不知道对方存在,耦合更低。

  • 项目里只有三个分支,同事非要抽成工厂加注册表,你怎么看?

    我会劝他先别抽。三个分支直接写一眼就能看懂,抽象之后反而要多跳两个文件。等分支真的多起来、或者要支持外部扩展时再重构,成本并不高。

webapp
公众号
开发者导航
切换夜间模式
点击侧边栏上一篇
点击侧边栏下一篇
折叠侧边栏
收起全部