代码写成一锅粥?3个设计模式让你的项目“起死回生”
你的组件里是不是全是
if-else?改一个地方,崩三个地方?新来的同事改完你的代码,你看着他,他看你,两人都沉默了。今天我们不背理论,直接用3个前端最常用的设计模式——单例、观察者、策略,把业务从“屎山”变成“积木”。学完你就能拍着胸脯说:“我的代码,谁都敢动。”
前言
设计模式不是“面试八股文”,而是前辈们踩过的坑总结成的“套路”。就像做饭有菜谱,写代码也有标准解法。今天我们把场景摆出来:弹窗多次打开、购物车更新通知到处写、表单校验if-else十几层……然后一个个用设计模式把它们治好。
一、单例模式:全局只有一个的“独生子”
场景:你写了个全局弹窗(Modal),用户点按钮就打开。结果用户连续点三次,页面上冒出三个弹窗叠在一起,像俄罗斯方块。
问题代码
function showModal() {
const div = document.createElement('div');
div.className = 'modal';
div.innerHTML = '我是弹窗';
document.body.appendChild(div);
}
// 点三次,三个弹窗
单例模式解决
确保无论调用多少次,只创建同一个实例。
class GlobalModal {
constructor() {
if (!GlobalModal.instance) {
this.element = null;
GlobalModal.instance = this;
}
return GlobalModal.instance;
}
show() {
if (!this.element) {
this.element = document.createElement('div');
this.element.className = 'modal';
this.element.innerHTML = '我是弹窗';
document.body.appendChild(this.element);
}
this.element.style.display = 'block';
}
hide() {
if (this.element) this.element.style.display = 'none';
}
}
const modal1 = new GlobalModal();
const modal2 = new GlobalModal();
console.log(modal1 === modal2); // true
真实项目更简单的写法:直接导出实例对象。
// modal.js
export const globalModal = {
element: null,
show() { /* ... */ },
hide() { /* ... */ }
};
应用:全局Store(Pinia/Vuex就是单例)、全局轮询管理器、WebSocket连接。
二、观察者模式:让不相干的组件“悄悄对话”
场景:用户点击“添加购物车”,需要同时做三件事:更新购物车角标、弹出“添加成功”提示、发送埋点数据。如果直接在购物车里调用其他模块的方法,代码会变成:
function addToCart(item) {
// 添加逻辑...
header.updateBadge(count);
toast.show('添加成功');
analytics.track('add_to_cart', item);
}
每加一个功能,addToCart就要改一次,耦合得像麻花。
观察者模式解决(事件总线)
// eventBus.js
class EventBus {
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(cb => cb(data));
}
}
off(event, callback) {
if (this.events[event]) {
this.events[event] = this.events[event].filter(cb => cb !== callback);
}
}
}
export const bus = new EventBus();
// 购物车模块
import { bus } from './eventBus';
function addToCart(item) {
// 添加逻辑...
bus.emit('cartUpdated', { count: newCount, item });
}
// 头部模块
import { bus } from './eventBus';
bus.on('cartUpdated', (data) => {
updateBadge(data.count);
});
// 埋点模块
bus.on('cartUpdated', (data) => {
analytics.track('add_to_cart', data.item);
});
现在,要加新功能只管bus.on,不用改购物车代码。Vue的emitter、React的useContext+useReducer其实都用了这个思想。
三、策略模式:消灭if-else毒瘤
场景:用户等级不同,商品折扣不同。你写了一个函数:
function getDiscount(level, price) {
if (level === 'normal') return price * 0.95;
else if (level === 'gold') return price * 0.9;
else if (level === 'platinum') return price * 0.8;
else return price;
}
这还好。但当你需要增加“钻石会员”、“企业会员”、“节日特惠”……函数越来越大,改一次心惊胆战。
策略模式解决:把算法抽成独立对象
const discountStrategies = {
normal: (price) => price * 0.95,
gold: (price) => price * 0.9,
platinum: (price) => price * 0.8,
};
function getDiscount(level, price) {
const strategy = discountStrategies[level];
return strategy ? strategy(price) : price;
}
新增会员等级,只需要加一个策略,不用改getDiscount。
更复杂的例子:表单验证
const validators = {
required: (val) => val && val.trim() !== '',
minLength: (val, len) => val.length >= len,
email: (val) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val),
};
function validateField(value, rules) {
for (let rule of rules) {
const [name, param] = rule.split(':');
const validator = validators[name];
if (validator && !validator(value, param)) {
return false;
}
}
return true;
}
// 使用
const isValid = validateField('abc@test.com', ['required', 'email']);
以后增加“手机号验证”,加一个mobile策略即可,完全符合开闭原则(对扩展开放,对修改封闭)。
四、组合实战:一个购物车结算页面
- 单例:全局唯一的购物车实例(存储商品列表)。
- 观察者:商品数量变化时,触发价格重算、优惠券校验、埋点。
- 策略:根据用户等级计算折扣;根据优惠券类型(满减、打折)计算优惠。
// 购物车单例
class Cart {
static instance = null;
static getInstance() {
if (!Cart.instance) Cart.instance = new Cart();
return Cart.instance;
}
items = [];
addItem(item) {
// 添加逻辑
bus.emit('cartChanged', this.items);
}
}
// 价格计算模块监听变化并应用折扣策略
bus.on('cartChanged', (items) => {
const total = items.reduce((sum, item) => sum + item.price * item.count, 0);
const discount = discountStrategies[user.level](total);
renderTotal(discount);
});
各模块独立,改折扣策略不影响购物车;加埋点不影响价格计算。
五、总结:模式是工具,不是教条
- 单例:保证全局唯一,适合共享资源。
- 观察者:解耦事件发布和订阅,适合跨组件通信。
- 策略:消除if-else,算法可互换,适合规则多变场景。
不要为了用模式而用模式。当你的代码出现重复、难维护、改一处动全身时,想想哪种模式能帮你“抽出来”。写代码就像搭积木,模式就是那些标准接口的积木块,让你搭得又快又稳。