目录
[一. 什么是设计模式?](#一. 什么是设计模式?)
[1. 语言特性:弱类型、原型继承,让"类式模式"变"轻量"](#1. 语言特性:弱类型、原型继承,让“类式模式”变“轻量”)
[2. 场景核心:DOM、异步、组件化,让模式"靶向落地"](#2. 场景核心:DOM、异步、组件化,让模式“靶向落地”)
[3. 框架影响:"封装好的模式"让开发者"不用造轮子"](#3. 框架影响:“封装好的模式”让开发者“不用造轮子”)
[4. 函数式倾向:"函数优先"让模式更简洁](#4. 函数式倾向:“函数优先”让模式更简洁)
1.简介
一. 什么是设计模式?
小册中将设计模式类比为"厨具""数学公式",强调其"解决重复问题的现成方案"属性。在此基础上,我们可以补充设计模式的核心三要素(源自GOF《设计模式:可复用面向对象软件的基础》),让定义更完整:
- 问题(Problem)
设计模式针对的是"在特定场景下反复出现的共性问题",而非孤立的偶发需求。
例如:
-
- 单例模式解决"一个模块只需要一个实例(如全局状态管理)"的问题;
- 观察者模式解决"一个对象变化时,多个依赖对象需同步响应(如状态更新触发UI重渲染)"的问题。
这些问题的核心矛盾往往是"变化与稳定的冲突"------这也呼应了小册强调的"设计模式的核心是封装变化"。
- 解决方案(Solution)
不是"代码模板",而是"抽象的设计思路"。它规定了对象间的职责划分、交互方式 ,而非具体的代码实现(不同语言需适配自身特性)。
例如"工厂模式"的解决方案核心是"将对象创建逻辑与使用逻辑分离",在JavaScript中可能用函数实现,在Java中则用类和接口实现。 - 效果(Consequences)
设计模式的应用会带来明确的利弊,需结合场景权衡。
例如:
-
- 装饰器模式能灵活给对象加功能(利:符合开放封闭原则),但会增加对象层级(弊:调试复杂度上升);
- 单例模式确保全局唯一实例(利:避免资源浪费),但会引入"全局状态"(弊:可能导致模块间耦合)。
此外,设计模式还存在层级关系,并非孤立存在:
- 顶层:设计原则(如SOLID)------ 设计模式的"指导思想",小册提到前端重点关注"单一功能""开放封闭",正是因为这两个原则直接解决前端最常见的"需求迭代频繁""代码复用难"问题;
- 中层:GOF 23种模式(创建型/结构型/行为型)------ 原则的"具体落地",小册的核心内容就是将这些模式适配到JavaScript中;
- 底层:前端实践技巧(如组件封装、状态管理方案)------ 模式在前端场景的"最终应用",比如Vuex的单例、React的HOC(装饰器模式)。
二、前端设计模式的"不一样"
小册提到设计模式需从Java/C++"迁移"到JavaScript,核心差异源于语言特性 和前端独特场景。具体可拆解为4个维度:
1. 语言特性:弱类型、原型继承,让"类式模式"变"轻量"
Java/C++是强类型、基于类的面向对象语言,设计模式依赖"类、接口、继承"等特性;而JavaScript是弱类型、基于原型的语言(ES6 Class只是原型的语法糖,并非真正的类),这导致前端设计模式必须"去重""简化":
|------|---------------------|-------------------|-----------------------------------------|
| 对比维度 | Java设计模式 | 前端设计模式 | 例子(小册相关) |
| 核心载体 | 类(Class) | 函数、对象、原型 | 工厂模式不用"抽象类",用函数返回对象;原型模式直接基于prototype |
| 接口依赖 | 必须通过interface定义契约 | 用"对象结构""函数参数约束"模拟 | 抽象工厂模式不用接口,用对象字面量定义"产品族"(如不同主题的按钮、输入框) |
| 继承使用 | 依赖类继承实现复用 | 优先用"组合"而非继承 | 装饰器模式不用"子类继承父类",而是用函数"包裹"原对象(如给组件加日志功能) |
例如小册中的"原型模式",Java中可能需要定义"原型类"+"克隆方法",而JavaScript直接用Object.create(prototypeObj)或obj.__proto__就能实现对象复用------这就是语言特性带来的"轻量适配"。
2. 场景核心:DOM、异步、组件化,让模式"靶向落地"
前端的核心场景是"与用户交互""操作DOM""处理异步",这些场景在后端几乎不存在,导致设计模式的"应用优先级"和"实现方式"完全不同:
(1)DOM操作:让"结构型模式"更重要
前端需频繁处理"DOM与逻辑的解耦""DOM复用",结构型模式(如代理、适配器、装饰器)成为高频工具:
- 代理模式:用"虚拟DOM"代理真实DOM(如Vue/React),避免频繁操作DOM导致性能问题(小册11-12节提到代理模式"隔离直接交互",这里就是典型应用);
- 适配器模式:适配不同DOM API的差异(如兼容
document.querySelector和IE的document.getElementById,小册10节提到"兼容代码一把梭",正是这个场景); - 装饰器模式:给DOM元素动态加功能(如给按钮加"防抖点击""权限控制",不用修改按钮本身的代码)。
(2)异步场景:让"行为型模式"适配"非同步逻辑"
后端多是"同步调用",而前端充满AJAX、Promise、定时器等异步操作,行为型模式需调整以处理"异步流":
- 观察者模式:后端可能是"同步通知"(如一个对象修改后立即通知所有观察者),前端则需支持"异步通知"(如接口请求成功后,通知多个组件更新数据,小册15-16节的"钉钉群"例子,本质就是异步消息通知);
- 迭代器模式:后端迭代"集合对象"是同步的,前端可能需要迭代"异步数据流"(如用
async/await迭代多个接口请求结果,小册17节提到"遍历专家",前端扩展为"异步遍历专家")。
(3)组件化:让"创建型模式"服务于"组件复用"
前端框架(Vue/React)的核心是组件化,创建型模式(工厂、单例)的应用场景完全围绕"组件创建"展开:
- 工厂模式:创建统一风格的组件(如"按钮工厂"根据参数返回"primary/secondary/danger"按钮,小册3-4节的"区分变与不变",这里"变的是按钮类型,不变的是创建逻辑");
- 单例模式:全局组件/状态的唯一实例(如Vuex的
store、全局弹窗组件,小册5节提到"Vuex的数据管理哲学",本质就是单例模式确保全局状态唯一)。
3. 框架影响:"封装好的模式"让开发者"不用造轮子"
后端设计模式往往需要开发者手动实现(如自己写单例类、工厂类),而前端框架已经将设计模式"内置封装",开发者更多是"理解原理"而非"重复实现":
- Vue:响应式用"代理模式"(
Proxy),组件通信用"观察者模式"(EventBus),全局状态用"单例模式"(Vuex); - React:组件复用用"装饰器模式"(HOC、
useDecorator),状态管理用"观察者模式"(Redux的subscribe); - 小册6节提到"单例模式面试真题",核心就是考察开发者是否理解"框架内置模式的实现逻辑"(如"如何手写一个Vuex-like的单例"),而非让开发者从零写单例。
4. 函数式倾向:"函数优先"让模式更简洁
小册评论区提到"前端多用函数式编程",这确实影响了设计模式的实现------前端更倾向用"函数"而非"类"实现模式,因为函数更轻量、更易组合:
- 策略模式:后端可能用"策略类"继承"抽象策略类",前端直接用"函数对象"(如表单验证规则,每个规则是一个函数,小册13节"拆分胖逻辑",用函数数组实现不同验证策略);
- 迭代器模式:后端用"迭代器类",前端用"生成器函数"(
function*)或"数组方法"(map/filter本质是迭代器的简化); - 这种"函数式适配"让前端设计模式摆脱了类的沉重,更符合JavaScript"函数是一等公民"的特性。
三、前端设计模式的核心
无论是通用设计模式的定义,还是前端的特殊性,核心都围绕小册强调的" 封装变化 ":
- 通用设计模式:通过"问题-方案-效果"的闭环,解决软件的"变化与稳定"冲突;
- 前端设计模式:在"弱类型、原型继承"的语言基础上,贴合"DOM、异步、组件化"的场景,用"轻量、函数式、框架适配"的方式,实现"变化最小化"。
对于前端开发者来说,学习设计模式不是"背模板",而是理解"模式背后的封装逻辑"------当遇到"需求迭代频繁""代码复用难""组件耦合高"时,能快速映射到对应的模式思路,这才是小册强调的"从映射到默写"的核心能力。