前端开发中常用的设计模式主要分为创建型、结构型和行为型三类,下面是针对前端高频使用的模式,逐一讲解其概念、使用场景并提供可直接运行的 Demo。
一、创建型模式
这类模式主要解决「对象创建」相关的问题,让对象创建更灵活、更可控。
1. 单例模式 (Singleton)
核心概念 :保证一个类仅有一个实例,并提供一个全局访问点。 使用场景:
- 全局缓存对象 - 弹窗/模态框组件(确保页面中只有一个实例)
- Vuex/Pinia 的 store 实例 - 全局事件总线 Demo 代码:
javascript
// 单例模式实现(通用版)
class SingletonModal {
constructor(content) {
// 如果已有实例,直接返回
if (SingletonModal.instance) {
return SingletonModal.instance;
}
this.content = content;
this.element = null;
// 缓存实例
SingletonModal.instance = this;
}
// 创建弹窗
DOM render() {
if (this.element)
return this.element;
const modal = document.createElement('div');
modal.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 20px;
background: white;
border: 1px solid #ccc;
z-index: 9999;
`;
modal.textContent = this.content;
this.element = modal;
document.body.appendChild(modal);
return modal;
}
// 关闭弹窗
close() {
if (this.element) {
document.body.removeChild(this.element);
this.element = null;
}
}
}
// 测试:多次创建只会得到同一个实例
const modal1 = new SingletonModal('这是第一个弹窗');
const modal2 = new SingletonModal('这是第二个弹窗');
console.log(modal1 === modal2); // true(验证单例)
modal1.render(); // 渲染弹窗(内容是"这是第一个弹窗",因为实例复用)
// modal1.close(); // 关闭弹窗
2. 工厂模式 (Factory)
核心概念 :定义一个创建对象的接口,让子类决定实例化哪一个类,将对象创建的逻辑封装起来。 使用场景:
- 根据不同参数创建不同类型的组件(如按钮、输入框)
- 数据格式化工具(根据数据类型返回不同格式)
- 网络请求适配器(根据环境选择 Axios/fetch)
Demo 代码:
javascript
// 定义不同类型的组件类
class Button {
constructor(text) {
this.text = text;
this.type = 'button';
}
render() {
return `<button>${this.text}</button>`;
}
}
class Input {
constructor(placeholder) {
this.placeholder = placeholder;
this.type = 'input';
}
render() {
return `<input type="text" placeholder="${this.placeholder}">`;
}
}
class Select {
constructor(options) {
this.options = options;
this.type = 'select';
}
render() {
const optionsHtml = this.options.map(opt => `<option value="${opt.value}">${opt.label}</option>`).join('');
return `<select>${optionsHtml}</select>`;
}
}
// 组件工厂类
class ComponentFactory {
static createComponent(type, config) {
switch (type) {
case 'button':
return new Button(config.text);
case 'input':
return new Input(config.placeholder);
case 'select':
return new Select(config.options);
default:
throw new Error(`不支持的组件类型:${type}`);
}
}
}
// 测试:通过工厂创建不同组件
const button = ComponentFactory.createComponent('button', { text: '提交' });
const input = ComponentFactory.createComponent('input', { placeholder: '请输入姓名' });
const select = ComponentFactory.createComponent('select', { options: [{ label: '男', value: 'male' }, { label: '女', value: 'female' }] });
console.log(button.render()); // <button>提交</button>
console.log(input.render()); // <input type="text" placeholder="请输入姓名">
console.log(select.render()); // <select><option value="male">男</option><option value="female">女</option></select>
二、结构型模式
这类模式关注对象的组合,优化类或对象的结构,提高代码的复用性和灵活性。
1. 代理模式 (Proxy)
核心概念 :为另一个对象提供一个替身或占位符,以控制对这个对象的访问。 使用场景:
- 图片懒加载(代理控制图片加载时机)
- 权限控制(代理拦截无权限的操作)
- 数据缓存(代理缓存重复请求的结果)
- Vue3 的响应式原理(基于 ES6 Proxy 实现) Demo 代码(图片懒加载):
javascript
// 真实的图片加载类
class RealImage {
constructor(url) {
this.url = url;
this.loadImage();
}
// 实际加载图片
loadImage() {
console.log(`开始加载图片:${this.url}`);
// 模拟图片加载耗时
setTimeout(() => {
console.log(`图片 ${this.url} 加载完成`);
}, 1000);
}
// 渲染图片
render(container) {
const img = document.createElement('img');
img.src = this.url;
img.alt = '示例图片';
container.appendChild(img);
}
}
// 图片代理类(懒加载)
class ImageProxy {
constructor(url) {
this.url = url;
this.realImage = null;
// 延迟创建真实图片实例
this.placeholder = 'https://via.placeholder.com/100x100?text=Loading'; // 占位图
}
// 代理渲染逻辑
render(container) {
// 先渲染占位图
const placeholderImg = document.createElement('img');
placeholderImg.src = this.placeholder;
placeholderImg.alt = '加载中';
container.appendChild(placeholderImg);
// 模拟滚动到可视区域后加载真实图片
setTimeout(() => {
this.realImage = new RealImage(this.url);
// 替换占位图
container.removeChild(placeholderImg);
this.realImage.render(container);
}, 2000);
}
}
// 测试:使用代理加载图片
const container = document.getElementById('image-container') || document.body;
const imageProxy = new ImageProxy('https://picsum.photos/400/300');
imageProxy.render(container);
2. 装饰器模式 (Decorator)
核心概念 :动态地给一个对象添加一些额外的职责,而不改变其原有结构。 使用场景:
- 给组件添加额外功能(如按钮添加防抖、输入框添加校验)
- 权限装饰(给不同角色的按钮添加不同操作权限)
- 日志装饰(给函数添加日志记录功能)
Demo 代码(函数装饰器):
javascript
// 基础函数:提交表单
function submitForm(data) {
console.log('提交表单数据:', data);
return { code: 200, msg: '提交成功' };
}
// 装饰器1:防抖装饰
function debounceDecorator(fn, delay = 500) {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => { fn.apply(this, args); }, delay);
};
}
// 装饰器2:日志装饰
function logDecorator(fn) {
return function(...args) {
console.log(`[${new Date().toLocaleString()}] 执行函数:${fn.name}`);
const result = fn.apply(this, args);
console.log(`[${new Date().toLocaleString()}] 函数执行结果:`, result);
return result;
};
}
// 装饰器3:校验装饰
function validateDecorator(fn) {
return function(...args) {
const data = args[0];
if (!data || !data.username) {
console.error('校验失败:用户名不能为空');
return { code: 400, msg: '用户名不能为空' };
}
return fn.apply(this, args);
};
}
// 组合装饰器:给提交函数添加防抖+日志+校验
const decoratedSubmit = debounceDecorator(logDecorator(validateDecorator(submitForm)));
// 测试
decoratedSubmit({ username: '' }); // 校验失败
decoratedSubmit({ username: 'zhangsan', age: 20 }); // 防抖后执行,带日志
decoratedSubmit({ username: 'zhangsan', age: 20 }); // 重复点击被防抖拦截
三、行为型模式
这类模式关注对象之间的通信和交互,优化对象的行为逻辑。
1. 观察者模式 (Observer)
核心概念 :定义对象间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象都会收到通知并自动更新。 使用场景:
- 事件监听(如 DOM 事件、自定义事件)
- 发布订阅系统(如 Vue 的 EventBus)
- 状态管理(如 React 状态更新、Vue 的响应式)
- 消息通知系统
Demo 代码(自定义事件总线):
javascript
// 观察者模式实现(事件总线)
class EventBus {
constructor() {
this.events = {}; // 存储事件和对应的回调函数
}
// 订阅事件
on(eventName, callback) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(callback);
}
// 取消订阅
off(eventName, callback) {
if (!this.events[eventName])
return;
if (callback) {
// 移除指定回调
this.events[eventName] = this.events[eventName].filter(fn => fn !== callback);
} else {
// 清空该事件所有回调
this.events[eventName] = [];
}
}
// 发布事件(触发)
emit(eventName, ...args) {
if (!this.events[eventName])
return;
// 执行所有订阅的回调
this.events[eventName].forEach(callback => { callback.apply(this, args); });
}
// 一次性订阅
once(eventName, callback) {
const wrapCallback = (...args) => {
callback.apply(this, args);
this.off(eventName, wrapCallback); // 执行后取消订阅
};
this.on(eventName, wrapCallback);
}
}
// 测试:使用事件总线
const bus = new EventBus();
// 订阅事件
const handleMsg = (msg) => {
console.log('收到消息:', msg);
};
bus.on('message', handleMsg);
// 一次性订阅
bus.once('onceMsg', (msg) => {
console.log('一次性消息:', msg);
});
// 发布事件
bus.emit('message', 'Hello Observer Pattern'); // 收到消息:Hello Observer Pattern
bus.emit('onceMsg', 'This is once'); // 一次性消息:This is once
bus.emit('onceMsg', 'This will not be received'); // 无输出
// 取消订阅
bus.off('message', handleMsg); bus.emit('message', 'This will not be received'); // 无输出
2. 策略模式 (Strategy)
核心概念 :定义一系列算法,将每个算法封装起来,并使它们可以互相替换,让算法的变化独立于使用算法的客户端。 使用场景:
- 表单校验规则(不同字段用不同校验策略)
- 支付方式选择(支付宝/微信/银行卡)
- 排序算法切换(快速排序/冒泡排序)
- 价格计算策略(普通用户/VIP/超级VIP)
Demo 代码(表单校验):
javascript
// 定义校验策略
const validateStrategies = {
// 非空校验
required: (value, errorMsg) => {
if (value === '' || value === undefined || value === null) {
return errorMsg;
}
},
// 最小长度校验
minLength: (value, length, errorMsg) => {
if (value && value.length < length) {
return errorMsg;
}
},
// 手机号校验
mobile: (value, errorMsg) => {
const reg = /^1[3-9]\d{9}$/;
if (value && !reg.test(value)) {
return errorMsg;
}
},
// 邮箱校验
email: (value, errorMsg) => {
const reg = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
if (value && !reg.test(value)) {
return errorMsg;
}
}
};
// 校验器类
class Validator {
constructor() {
this.rules = []; // 存储校验规则
}
// 添加校验规则
add(value, rules) {
rules.forEach(rule => {
const { strategy, errorMsg, param } = rule;
this.rules.push(() => {
// 拆分策略(如 minLength:6 拆分为 minLength 和 6)
const [strategyName, strategyParam] = strategy.split(':');
return validateStrategies[strategyName](value, strategyParam || param, errorMsg);
});
});
}
// 执行校验
validate() {
for (const rule of this.rules) {
const errorMsg = rule();
if (errorMsg) {
return errorMsg; // 有错误立即返回
}
}
return ''; // 校验通过
}
}
// 测试:表单校验
const formData = {
username: '',
password: '123',
mobile: '1234567890',
email: 'test@example'
};
// 创建校验器并添加规则
const validator = new Validator();
validator.add(formData.username, [{ strategy: 'required', errorMsg: '用户名不能为空' }]);
validator.add(formData.password, [ { strategy: 'required', errorMsg: '密码不能为空' }, { strategy: 'minLength:6', errorMsg: '密码长度不能少于6位' } ]);
validator.add(formData.mobile, [{ strategy: 'mobile', errorMsg: '手机号格式错误' }]);
validator.add(formData.email, [{ strategy: 'email', errorMsg: '邮箱格式错误' }]);
// 执行校验
const errorMsg = validator.validate();
console.log(errorMsg); // 用户名不能为空(第一个错误)
总结
前端开发中高频使用的设计模式及核心要点:
- 单例模式:保证唯一实例,适用于全局组件/缓存/事件总线,核心是缓存实例并复用。
- 工厂模式:封装对象创建逻辑,适用于多类型组件/工具创建,核心是根据参数返回不同实例。
- 代理模式:控制对象访问,适用于懒加载/权限控制/缓存,核心是「替身」拦截并处理逻辑。
- 装饰器模式:动态扩展功能,适用于函数增强/组件扩展,核心是不修改原对象仅添加职责。
- 观察者模式:一对多通知,适用于事件系统/状态管理,核心是发布-订阅的解耦通信。
- 策略模式 :算法封装与替换,适用于校验/支付/排序,核心是将算法与使用逻辑分离。 这些模式的核心价值是解耦、复用、可扩展,实际开发中不必生搬硬套,而是根据场景灵活运用,比如 Vue/React 框架内部就大量使用了这些模式(如 React 的合成事件用了观察者模式,Vue3 的响应式用了代理模式)。