跳转到内容

前端常用设计模式

本文主要对一部分前端常用的设计模式进行记录,虽然前端日常开发中可能涉及到面向对象不是那么多,但是学习设计模式的思想有助于我们提升代码质量。

关于其他设计模式的进一步学习,可参考 设计模式

核心思想:

单例模式确保一个类只有一个实例,并提供全局访问点。它通过私有化构造函数和提供静态方法来控制实例的创建。

前端应用场景:

  • 全局状态管理:如 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; // 访问属性: name
data.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); // 邮箱格式不正确

核心思想:

将请求的发送者和多个潜在接收者解耦,并将这些接收者连成一条链。请求沿链传递,直到链上的一个对象处理它为止。

关键特点:

  • 链式结构:处理器对象通过引用下一个处理器组成一条链。

  • 传递请求:请求从链首开始,依次经过每个处理器。

  • 处理或传递:每个处理器可决定自行处理请求,或将其传递给下一个。

优势:

增强了请求处理的灵活性,可以动态地添加、修改或排序处理器,且发送者无需关心具体由谁处理。

典型案例:

  1. Webpack Loader 对文件资源的处理过程就是一条责任链。例如,处理.scss文件时,链可能为:sass-loader -> css-loader -> style-loader。每个 Loader 只专注一个任务(编译、转换、注入),处理完后将结果传给下一个。其中的 pitch 机制更是允许 loader 中途拦截请求,是责任链的典型体现。

  2. Axios 拦截器:它拥有请求和响应两条独立的链。请求发出前,config对象会依次经过所有请求拦截器进行增强(如添加 Token)。响应返回后,response对象也会依次经过所有响应拦截器进行处理(如解析数据、统一报错)。每个拦截器都能决定修改后传递、中断链条或抛出错误。