7种设计模式
设计模式按功能分3类,本文覆盖全部核心类型,掌握这7种就能应对80%的前端场景:
-
结构型:亨元模式、单例模式(优化代码结构,提升复用)
-
行为型:备忘录、中介者、迭代器、发布-订阅(规范对象交互,降低耦合)
-
创建型:工厂模式(规范对象创建,简化逻辑)
一、结构型模式:让代码结构更灵活
核心解决"类/对象怎么组合"的问题,重点是复用和灵活扩展。
1. 亨元模式:能共享的就不重复造
核心思想:大量相似对象只创建一次公共部分,独有的部分单独传参,节省内存。
前端场景:页面渲染多个相同样式的图标(比如列表里的"编辑"图标)
案例:
javascript
// 1. 共享的图标对象(公共部分:图标类型、地址)
class Icon {
constructor(type) {
this.type = type; // 比如"编辑""删除"
this.url = `/icons/${type}.png`; // 共享的图标地址
}
// 渲染时传独有的位置(外部状态)
render(x, y) {
console.log(`渲染${this.type}图标,位置(${x},${y})`);
}
}
// 2. 管理共享对象的工厂:确保同类型只创建一次
class IconFactory {
constructor() {
this.iconMap = {}; // 缓存已创建的图标
}
getIcon(type) {
// 有就复用,没有就新建
if (!this.iconMap[type]) {
this.iconMap[type] = new Icon(type);
}
return this.iconMap[type];
}
}
// 使用:渲染10个编辑图标,实际只创建1个对象
const factory = new IconFactory();
for (let i = 0; i < 10; i++) {
// 复用同一个"编辑"图标,只改位置
factory.getIcon('edit').render(i * 50, 10);
}
console.log(Object.keys(factory.iconMap).length); // 1(只创建了1个对象)
2. 单例模式:全局只有一个实例
核心思想:一个类在整个应用里只有一个对象,避免重复创建浪费资源。
前端场景:全局的Toast提示、弹窗管理器(整个页面只能有一个Toast)
案例:
javascript
// 全局唯一的Toast管理器
class Toast {
constructor() {
this.showing = false; // 标记是否正在显示
}
// 显示Toast
show(text) {
if (this.showing) return; // 避免重复显示
this.showing = true;
console.log(`显示Toast:${text}`);
// 3秒后隐藏
setTimeout(() => {
this.showing = false;
}, 3000);
}
// 静态方法:获取全局唯一实例
static getInstance() {
if (!Toast.instance) {
Toast.instance = new Toast();
}
return Toast.instance;
}
}
// 使用:多次调用,都是同一个实例
const toast1 = Toast.getInstance();
const toast2 = Toast.getInstance();
toast1.show('操作成功'); // 显示Toast:操作成功
toast2.show('重复调用'); // 不生效(正在显示中)
console.log(toast1 === toast2); // true(同一个实例)
二、行为型模式:让对象交互更高效
核心解决"对象之间怎么通信、怎么控制行为"的问题,重点是解耦。
1. 备忘录模式:保存状态,支持撤销
核心思想:偷偷保存对象的状态,后续想恢复时直接用,不破坏原代码。
前端场景:输入框的撤销功能、表单重置
案例:
javascript
// 1. 备忘录:专门保存状态(只存不改)
class Memento {
constructor(state) {
this.state = state; // 保存输入框的内容
}
getState() {
return this.state; // 提供获取状态的方法
}
}
// 2. 输入框(需要保存状态的对象)
class InputBox {
constructor() {
this.value = ''; // 输入框当前值
}
// 更新输入值
setValue(val) {
this.value = val;
}
// 保存当前状态到备忘录
saveState() {
return new Memento(this.value);
}
// 从备忘录恢复状态(撤销)
restoreState(memento) {
this.value = memento.getState();
}
}
// 使用:输入框撤销功能
const input = new InputBox();
const history = []; // 保存历史状态
// 第一步:输入"hello",保存状态
input.setValue('hello');
history.push(input.saveState());
console.log('当前输入:', input.value); // hello
// 第二步:输入"world",保存状态
input.setValue('hello world');
history.push(input.saveState());
console.log('当前输入:', input.value); // hello world
// 第三步:撤销到上一步
input.restoreState(history[0]);
console.log('撤销后:', input.value); // hello
2. 中介者模式:所有交互都走"中间人"
核心思想:多个对象不直接通信,都通过一个"中介者"传话,降低耦合。
前端场景:兄弟组件通信、聊天室消息转发
案例:
javascript
// 1. 中介者(中间人:转发消息)
class Mediator {
constructor() {
this.users = []; // 所有需要通信的对象
}
// 注册对象
addUser(user) {
this.users.push(user);
user.mediator = this; // 给对象绑定中介者
}
// 转发消息:A发的消息,转给其他人
sendMsg(msg, sender) {
this.users.forEach(user => {
if (user !== sender) { // 不发给自己
user.receiveMsg(msg);
}
});
}
}
// 2. 通信对象(比如聊天室用户)
class User {
constructor(name) {
this.name = name;
}
// 发消息(通过中介者)
sendMsg(msg) {
console.log(`${this.name}发送:${msg}`);
this.mediator.sendMsg(msg, this);
}
// 收消息
receiveMsg(msg) {
console.log(`${this.name}收到:${msg}`);
}
}
// 使用:3人聊天室
const mediator = new Mediator();
const user1 = new User('张三');
const user2 = new User('李四');
mediator.addUser(user1);
mediator.addUser(user2);
user1.sendMsg('你好');
// 输出:张三发送:你好 → 李四收到:你好
3. 迭代器模式:用统一方式遍历不同数据
核心思想:不管是数组、对象,都用同样的方法遍历,不用关心底层结构。
前端场景:遍历不同数据源(比如同时遍历数组和对象)
案例:
javascript
// 迭代器:统一遍历逻辑
class Iterator {
constructor(data) {
this.data = data;
this.index = 0;
// 把对象转为数组,方便统一处理
if (typeof data === 'object' && !Array.isArray(data)) {
this.data = Object.entries(data);
}
}
// 遍历所有元素,执行回调
each(callback) {
while (this.index < this.data.length) {
callback(this.data[this.index]);
this.index++;
}
}
}
// 使用:用同样的each方法遍历数组和对象
// 1. 遍历数组
new Iterator([1, 2, 3]).each(item => {
console.log('数组元素:', item); // 1 2 3
});
// 2. 遍历对象
new Iterator({ name: '张三', age: 20 }).each(([key, val]) => {
console.log('对象属性:', `${key}=${val}`); // name=张三 age=20
});
4. 发布-订阅模式:事件驱动,灵活通信
核心思想:有人"发布"事件,有人"订阅"事件,发布后所有订阅者都会收到通知。
前端场景:跨层级组件通信、全局事件监听(比如页面滚动回调)
案例:
javascript
// 事件总线(发布-订阅中心)
class EventBus {
constructor() {
this.events = {}; // 存储事件:{ 事件名: [回调1, 回调2] }
}
// 订阅事件(监听)
on(eventName, callback) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(callback);
}
// 发布事件(触发)
emit(eventName, ...args) {
// 触发该事件的所有回调
this.events[eventName]?.forEach(callback => {
callback(...args);
});
}
}
// 使用:全局事件通信
const eventBus = new EventBus();
// 订阅"login"事件(监听登录成功)
eventBus.on('login', (username) => {
console.log(`欢迎${username}登录`);
});
// 发布"login"事件(触发登录成功)
eventBus.emit('login', '张三'); // 输出:欢迎张三登录
三、创建型模式:让对象创建更简单
核心解决"怎么创建对象"的问题,重点是简化创建逻辑,避免重复代码。
工厂模式:找工厂要对象,不用自己new
核心思想:把对象的创建逻辑封装在工厂里,想要对象时直接找工厂要,不用关心创建细节。
前端场景:创建不同类型的弹窗(成功、错误、警告弹窗)
案例:
javascript
// 1. 不同类型的弹窗(产品)
class SuccessAlert {
show(msg) {
console.log(`正缺${msg}`);
}
}
class ErrorAlert {
show(msg) {
console.log(`错误 ${msg}`);
}
}
// 2. 弹窗工厂(封装创建逻辑)
class AlertFactory {
// 根据类型创建不同弹窗
createAlert(type) {
switch (type) {
case 'success':
return new SuccessAlert();
case 'error':
return new ErrorAlert();
}
}
}
// 使用:通过工厂创建弹窗,不用自己new
const factory = new AlertFactory();
const successAlert = factory.createAlert('success');
successAlert.show('操作成功'); // 操作成功
const errorAlert = factory.createAlert('error');
errorAlert.show('操作失败'); // 操作失败
四、7种模式速查表(快速记忆)
| 模式名称 | 核心思想 | 前端简单场景 |
|---|---|---|
| 亨元模式 | 共享相似对象,减少创建 | 渲染大量相同图标 |
| 单例模式 | 全局唯一实例 | 全局Toast管理器 |
| 备忘录模式 | 保存状态,支持撤销 | 输入框撤销 |
| 中介者模式 | 通过中间人通信,解耦 | 兄弟组件通信 |
| 迭代器模式 | 统一遍历不同数据 | 遍历数组/对象 |
| 发布-订阅模式 | 事件驱动,发布订阅 | 跨层级组件通信 |
| 工厂模式 | 工厂封装创建逻辑 | 创建不同类型弹窗 |