前端常用设计模式
本文主要对一部分前端常用的设计模式进行记录,虽然前端日常开发中可能涉及到面向对象不是那么多,但是学习设计模式的思想有助于我们提升代码质量。
关于其他设计模式的进一步学习,可参考 设计模式 。
核心思想:
单例模式确保一个类只有一个实例,并提供全局访问点。它通过私有化构造函数和提供静态方法来控制实例的创建。
前端应用场景:
- 全局状态管理:如 Vuex Store、Redux Store 等状态管理器
- API 请求管理:创建统一的 HTTP 客户端实例,避免重复配置
- 日志记录器:全局日志管理,统一日志格式和输出
- 缓存管理:浏览器缓存、本地存储的统一管理
// 单例模式示例:API 管理器class ApiManager { constructor() { if (ApiManager.instance) { return ApiManager.instance; } this.baseURL = 'https://api.example.com'; ApiManager.instance = this; }
static getInstance() { if (!ApiManager.instance) { ApiManager.instance = new ApiManager(); } return ApiManager.instance; }
async request(url, options) { return fetch(`${this.baseURL}${url}`, options); }}
// 使用const api1 = ApiManager.getInstance();const api2 = ApiManager.getInstance();console.log(api1 === api2); // true核心思想:
装饰器模式允许在不修改原对象结构的情况下,动态地给对象添加新的功能。它通过创建装饰器类来包装原对象,实现功能的扩展。
前端应用场景:
- 高阶组件(HOC):React 中为组件添加额外功能
- 中间件系统:Express.js、Koa.js 等框架的中间件
- 函数增强:为函数添加日志、缓存、防抖等功能
- UI 组件扩展:为基础组件添加样式、事件处理等
// 装饰器模式示例:函数装饰器function withLogging(fn) { return function(...args) { console.log(`调用函数 ${fn.name},参数:`, args); const result = fn.apply(this, args); console.log(`函数 ${fn.name} 返回:`, result); return result; };}
function withCache(fn) { const cache = new Map(); return function(...args) { const key = JSON.stringify(args); if (cache.has(key)) { console.log('从缓存获取结果'); return cache.get(key); } const result = fn.apply(this, args); cache.set(key, result); return result; };}
// 使用装饰器const add = (a, b) => a + b;const enhancedAdd = withCache(withLogging(add));核心思想:
代理模式为其他对象提供一个代理或占位符,以控制对原对象的访问。代理对象与原对象实现相同的接口,可以在访问前后添加额外的处理逻辑。
前端应用场景:
- 数据绑定:Vue.js 的响应式系统使用 Proxy 实现数据劫持
- API 代理:统一处理请求拦截、响应处理、错误处理
- 虚拟代理:图片懒加载、组件懒加载
- 保护代理:权限控制、参数验证
// 代理模式示例:响应式数据function createReactive(target) { return new Proxy(target, { get(obj, prop) { console.log(`访问属性: ${prop}`); return obj[prop]; }, set(obj, prop, value) { console.log(`设置属性: ${prop} = ${value}`); obj[prop] = value; // 触发视图更新 updateView(); return true; } });}
function updateView() { console.log('视图已更新');}
// 使用const data = createReactive({ name: 'Vue', version: 3 });data.name; // 访问属性: namedata.version = 4; // 设置属性: version = 4, 视图已更新核心思想:
观察者模式定义了对象间的一对多依赖关系,当一个对象状态发生改变时,所有依赖它的对象都会得到通知并自动更新。
前端应用场景:
- 事件系统:DOM 事件、自定义事件
- 状态管理:Redux、MobX 等状态变化通知
- 组件通信:父子组件、兄弟组件间的数据传递
- 模型-视图同步:MVC、MVVM 架构中的数据绑定
// 观察者模式示例:事件发布订阅class EventEmitter { constructor() { this.events = {}; }
// 订阅事件 on(event, callback) { if (!this.events[event]) { this.events[event] = []; } this.events[event].push(callback); }
// 发布事件 emit(event, data) { if (this.events[event]) { this.events[event].forEach(callback => callback(data)); } }
// 取消订阅 off(event, callback) { if (this.events[event]) { this.events[event] = this.events[event].filter(cb => cb !== callback); } }}
// 使用const emitter = new EventEmitter();emitter.on('userLogin', (user) => console.log(`用户 ${user.name} 登录`));emitter.on('userLogin', (user) => console.log(`更新用户状态`));emitter.emit('userLogin', { name: 'Alice' });核心思想:
策略模式定义了一系列算法,将每个算法封装起来,并使它们可以互相替换。策略模式让算法独立于使用它的客户端变化。
状态模式用于对象在不同状态下的通用操作的差异进行拆分;而策略模式更加侧重于策略选择,通常用于简化
if-else
前端应用场景:
- 表单验证:不同字段使用不同的验证策略
- 支付方式:支付宝、微信、银行卡等不同支付策略
- 动画效果:缓动函数、过渡效果的选择
- 数据格式化:日期、货币、数字等不同格式化策略
// 策略模式示例:表单验证class Validator { constructor() { this.strategies = {}; }
// 添加验证策略 addStrategy(name, fn) { this.strategies[name] = fn; }
// 执行验证 validate(value, rules) { for (let rule of rules) { const [strategyName, errorMsg] = rule.split(':'); if (!this.strategies[strategyName](value)) { return errorMsg || `${strategyName} 验证失败`; } } return null; }}
// 定义验证策略const validator = new Validator();validator.addStrategy('required', (value) => value && value.trim() !== '');validator.addStrategy('email', (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value));validator.addStrategy('minLength', (value) => value && value.length >= 6);
// 使用const emailError = validator.validate('test@', ['required:邮箱不能为空', 'email:邮箱格式不正确']);console.log(emailError); // 邮箱格式不正确核心思想:
将请求的发送者和多个潜在接收者解耦,并将这些接收者连成一条链。请求沿链传递,直到链上的一个对象处理它为止。
关键特点:
-
链式结构:处理器对象通过引用下一个处理器组成一条链。
-
传递请求:请求从链首开始,依次经过每个处理器。
-
处理或传递:每个处理器可决定自行处理请求,或将其传递给下一个。
优势:
增强了请求处理的灵活性,可以动态地添加、修改或排序处理器,且发送者无需关心具体由谁处理。
典型案例:
-
Webpack Loader 对文件资源的处理过程就是一条责任链。例如,处理
.scss文件时,链可能为:sass-loader -> css-loader -> style-loader。每个 Loader 只专注一个任务(编译、转换、注入),处理完后将结果传给下一个。其中的 pitch 机制更是允许 loader 中途拦截请求,是责任链的典型体现。 -
Axios 拦截器:它拥有请求和响应两条独立的链。请求发出前,
config对象会依次经过所有请求拦截器进行增强(如添加 Token)。响应返回后,response对象也会依次经过所有响应拦截器进行处理(如解析数据、统一报错)。每个拦截器都能决定修改后传递、中断链条或抛出错误。