深入理解 JavaScript 设计模式
单例、观察者、发布订阅、策略、代理、装饰器、工厂模式,结合前端框架源码实战
什么是设计模式?
定义:设计模式(Design Pattern)是针对软件设计中反复出现的问题所总结出的通用解决方案。它不是具体的代码,而是一种经过验证的、可复用的设计思想和模板,帮助开发者写出可维护、可扩展、低耦合的代码。
涉及场景:
- 状态管理:Redux(发布订阅)、Vuex(观察者)的核心架构
- 组件通信:EventBus(发布订阅)、Context(中介者)
- 权限与拦截:axios 拦截器(装饰器/责任链)、路由守卫(策略)
- 实例管理:全局弹窗、日志服务(单例模式)
- UI 组件库:根据类型动态创建组件(工厂模式)
- 数据响应式:Vue3
reactive(代理模式)、计算属性(观察者)
作用:
- 提升代码质量:经过验证的方案避免重复踩坑
- 统一团队语言:团队成员用"观察者模式"、"策略模式"等术语沟通更高效
- 框架源码阅读:主流框架大量使用设计模式,理解模式才能读懂源码
- 面试加分项:能结合实际项目讲解设计模式的应用是中高级面试的加分点
创建型模式
单例模式(Singleton)
确保一个类只有一个实例,提供全局访问点。
javascript
// 方式1:闭包
const createSingleton = (function() {
let instance = null;
class Database {
constructor(config) {
if (instance) return instance;
this.config = config;
this.connections = [];
instance = this;
}
connect() {
console.log(`Connected to ${this.config.host}`);
}
}
return function(config) {
return new Database(config);
};
})();
const db1 = createSingleton({ host: 'localhost' });
const db2 = createSingleton({ host: 'remote' }); // 被忽略
console.log(db1 === db2); // true
// 方式2:ES Module(天然单例)
// store.js
class Store {
#state = {};
getState() { return this.#state; }
setState(newState) { this.#state = { ...this.#state, ...newState }; }
}
export const store = new Store();
// 每个 import 都得到同一个实例
// 方式3:代理实现
function singleton(ClassName) {
let instance;
return new Proxy(ClassName, {
construct(target, args) {
if (!instance) {
instance = Reflect.construct(target, args);
}
return instance;
}
});
}
const SingletonDB = singleton(class DB {
constructor(name) { this.name = name; }
});
// 实际应用:全局状态管理(Vuex/Redux store)、日志器、配置管理工厂模式(Factory)
javascript
// 简单工厂
class ButtonFactory {
static create(type, text) {
switch (type) {
case 'primary':
return { type, text, className: 'btn-primary', color: '#1890ff' };
case 'danger':
return { type, text, className: 'btn-danger', color: '#ff4d4f' };
case 'link':
return { type, text, className: 'btn-link', color: '#1890ff', border: 'none' };
default:
return { type: 'default', text, className: 'btn-default', color: '#333' };
}
}
}
const btn = ButtonFactory.create('primary', '提交');
// 抽象工厂
class UIFactory {
createButton() { throw new Error('必须实现'); }
createInput() { throw new Error('必须实现'); }
createModal() { throw new Error('必须实现'); }
}
class AntDesignFactory extends UIFactory {
createButton(props) { return { component: 'AntButton', ...props }; }
createInput(props) { return { component: 'AntInput', ...props }; }
createModal(props) { return { component: 'AntModal', ...props }; }
}
class MaterialFactory extends UIFactory {
createButton(props) { return { component: 'MuiButton', ...props }; }
createInput(props) { return { component: 'MuiTextField', ...props }; }
createModal(props) { return { component: 'MuiDialog', ...props }; }
}
// 根据主题选择工厂
function getFactory(theme) {
return theme === 'antd' ? new AntDesignFactory() : new MaterialFactory();
}
// 实际应用:React.createElement 就是工厂模式结构型模式
代理模式(Proxy Pattern)
javascript
// 虚拟代理:延迟加载大图
class ProxyImage {
#realImage = null;
#src;
constructor(src) {
this.#src = src;
// 先用占位图
this.element = document.createElement('img');
this.element.src = 'placeholder.jpg';
}
display() {
if (!this.#realImage) {
// 懒加载真实图片
this.#realImage = new Image();
this.#realImage.onload = () => {
this.element.src = this.#src;
};
this.#realImage.src = this.#src;
}
}
}
// 缓存代理
function createCachedApi(fetchFn) {
const cache = new Map();
return new Proxy(fetchFn, {
apply(target, thisArg, args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return Promise.resolve(cache.get(key));
}
return Reflect.apply(target, thisArg, args).then(result => {
cache.set(key, result);
return result;
});
}
});
}
const cachedFetch = createCachedApi(async (url) => {
const res = await fetch(url);
return res.json();
});
// 实际应用:Vue3 reactive()、ES6 Proxy、nginx 反向代理装饰器模式(Decorator)
javascript
// 函数装饰器
function withLogging(fn) {
return function(...args) {
console.log(`调用 ${fn.name}(${args.join(', ')})`);
const start = performance.now();
const result = fn.apply(this, args);
console.log(`完成,耗时 ${(performance.now() - start).toFixed(2)}ms`);
return result;
};
}
function withRetry(fn, maxRetries = 3) {
return async function(...args) {
for (let i = 0; i <= maxRetries; i++) {
try {
return await fn.apply(this, args);
} catch (err) {
if (i === maxRetries) throw err;
console.log(`重试 ${i + 1}/${maxRetries}`);
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i)));
}
}
};
}
// 组合装饰器
const fetchData = withLogging(withRetry(async (url) => {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}));
// TC39 装饰器提案(Stage 3)
// @logged
// class MyClass {
// @memoize
// expensiveMethod() {}
// }
// 实际应用:Express/Koa 中间件、React HOC、Angular 装饰器行为型模式
观察者模式(Observer)
javascript
// 观察者模式:Subject 直接通知 Observer
class EventEmitter {
#events = new Map();
on(event, listener) {
if (!this.#events.has(event)) {
this.#events.set(event, new Set());
}
this.#events.get(event).add(listener);
return this; // 链式调用
}
off(event, listener) {
const listeners = this.#events.get(event);
if (listeners) {
listeners.delete(listener);
if (listeners.size === 0) this.#events.delete(event);
}
return this;
}
once(event, listener) {
const wrapper = (...args) => {
listener.apply(this, args);
this.off(event, wrapper);
};
wrapper._original = listener; // 用于 off 匹配
return this.on(event, wrapper);
}
emit(event, ...args) {
const listeners = this.#events.get(event);
if (listeners) {
for (const listener of listeners) {
listener.apply(this, args);
}
}
return this;
}
listenerCount(event) {
return this.#events.get(event)?.size ?? 0;
}
removeAllListeners(event) {
if (event) {
this.#events.delete(event);
} else {
this.#events.clear();
}
return this;
}
}
// 使用
const emitter = new EventEmitter();
emitter.on('data', (data) => console.log('收到:', data));
emitter.once('connect', () => console.log('首次连接'));
emitter.emit('connect'); // '首次连接'
emitter.emit('connect'); // (无输出,once 只触发一次)
emitter.emit('data', { id: 1 }); // '收到: {id: 1}'发布订阅模式(Pub/Sub)
javascript
// 与观察者的区别:通过中间件(事件中心)解耦
class EventBus {
#channels = new Map();
subscribe(channel, callback, options = {}) {
if (!this.#channels.has(channel)) {
this.#channels.set(channel, []);
}
const subscription = {
callback,
once: options.once || false,
priority: options.priority || 0
};
const subs = this.#channels.get(channel);
subs.push(subscription);
// 按优先级排序
subs.sort((a, b) => b.priority - a.priority);
// 返回取消订阅函数
return () => {
const index = subs.indexOf(subscription);
if (index > -1) subs.splice(index, 1);
};
}
publish(channel, data) {
const subs = this.#channels.get(channel);
if (!subs) return;
const toRemove = [];
for (const sub of subs) {
sub.callback(data);
if (sub.once) toRemove.push(sub);
}
// 移除 once 订阅
for (const sub of toRemove) {
const index = subs.indexOf(sub);
if (index > -1) subs.splice(index, 1);
}
}
// 支持通配符
publishWildcard(pattern, data) {
for (const [channel, subs] of this.#channels) {
if (this.#matchPattern(pattern, channel)) {
this.publish(channel, data);
}
}
}
#matchPattern(pattern, channel) {
const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
return regex.test(channel);
}
}
// 观察者 vs 发布订阅
// 观察者:Subject 直接持有 Observer 引用,紧耦合
// 发布订阅:通过 EventBus 中介,发布者和订阅者互不知道对方
// 实际应用:
// Vue2 的 $emit/$on、Redux 的 store.subscribe
// Node.js EventEmitter、浏览器 CustomEvent
// 微前端通信、跨组件通信策略模式(Strategy)
javascript
// 将算法封装为独立的策略对象
const validationStrategies = {
required: (value) => ({
valid: value !== '' && value != null,
message: '此字段必填'
}),
email: (value) => ({
valid: /^\S+@\S+\.\S+$/.test(value),
message: '邮箱格式不正确'
}),
minLength: (min) => (value) => ({
valid: value.length >= min,
message: `最少 ${min} 个字符`
}),
maxLength: (max) => (value) => ({
valid: value.length <= max,
message: `最多 ${max} 个字符`
}),
pattern: (regex, msg) => (value) => ({
valid: regex.test(value),
message: msg
})
};
// 表单验证器
class FormValidator {
#rules = new Map();
addRule(field, ...strategies) {
this.#rules.set(field, strategies);
return this;
}
validate(data) {
const errors = {};
for (const [field, strategies] of this.#rules) {
for (const strategy of strategies) {
const result = strategy(data[field]);
if (!result.valid) {
errors[field] = result.message;
break;
}
}
}
return { valid: Object.keys(errors).length === 0, errors };
}
}
const validator = new FormValidator()
.addRule('email', validationStrategies.required, validationStrategies.email)
.addRule('password',
validationStrategies.required,
validationStrategies.minLength(8),
validationStrategies.maxLength(20)
);
validator.validate({ email: 'test@test.com', password: '12345678' });
// { valid: true, errors: {} }
// 实际应用:
// 表单验证、支付方式选择、排序算法选择
// Array.sort() 的比较函数就是策略模式迭代器模式(Iterator)
javascript
// ES6 的 Symbol.iterator 就是迭代器模式的标准实现
// 详见 Generator/Iterator 专题
// 实际应用:for...of、展开运算符、解构赋值
// React 中遍历子组件中介者模式(Mediator)
javascript
// 多个对象通过中介者通信,避免对象之间直接引用
class ChatRoom {
#users = new Map();
join(user) {
this.#users.set(user.name, user);
user.chatRoom = this;
this.broadcast(`${user.name} 加入了聊天室`, user);
}
leave(user) {
this.#users.delete(user.name);
this.broadcast(`${user.name} 离开了聊天室`, user);
}
send(message, from, to) {
const target = this.#users.get(to);
if (target) {
target.receive(message, from.name);
}
}
broadcast(message, excludeUser) {
for (const [name, user] of this.#users) {
if (user !== excludeUser) {
user.receive(message, 'System');
}
}
}
}
class User {
constructor(name) {
this.name = name;
this.chatRoom = null;
}
send(message, to) {
this.chatRoom.send(message, this, to);
}
receive(message, from) {
console.log(`[${this.name}] ${from}: ${message}`);
}
}
// 实际应用:聊天室、MVC中的Controller、Redux中间件前端框架中的设计模式
javascript
// React 中的模式
// 1. 组合模式(Composite)- 组件树
// 2. 工厂模式 - React.createElement
// 3. 观察者模式 - useState 触发重渲染
// 4. 策略模式 - render props
// 5. 代理模式 - React.lazy / forwardRef
// 6. 装饰器模式 - HOC(Higher Order Component)
// Vue 中的模式
// 1. 观察者模式 - 响应式系统(Watcher/Dep)
// 2. 代理模式 - Vue3 Proxy
// 3. 发布订阅 - $emit / $on
// 4. 策略模式 - 指令系统
// 5. 工厂模式 - createApp / defineComponent
// 6. 装饰器模式 - mixins(已不推荐)
// 中间件模式(Koa/Express/Redux)
function compose(middlewares) {
return function(context) {
let index = -1;
function dispatch(i) {
if (i <= index) return Promise.reject(new Error('next() called multiple times'));
index = i;
const fn = middlewares[i];
if (!fn) return Promise.resolve();
return Promise.resolve(fn(context, () => dispatch(i + 1)));
}
return dispatch(0);
};
}
// 使用
const middlewares = [
async (ctx, next) => {
console.log('1 start');
await next();
console.log('1 end');
},
async (ctx, next) => {
console.log('2 start');
await next();
console.log('2 end');
}
];
compose(middlewares)({});
// 1 start → 2 start → 2 end → 1 end(洋葱模型)总结
设计模式核心知识点:
┌──────────────────────────────────────────────────────────┐
│ 创建型 │
│ • 单例:全局唯一实例(Store、Config、Logger) │
│ • 工厂:封装对象创建逻辑(createElement、createApp) │
├──────────────────────────────────────────────────────────┤
│ 结构型 │
│ • 代理:控制对象访问(Proxy、lazy load、缓存) │
│ • 装饰器:增强功能(HOC、中间件、retry/logging) │
├──────────────────────────────────────────────────────────┤
│ 行为型 │
│ • 观察者:Subject 直接通知 Observer(DOM事件、Vue Watcher) │
│ • 发布订阅:通过事件中心解耦(EventBus、Redux) │
│ • 策略:封装算法族(表单验证、排序比较函数) │
│ • 中介者:对象通过中介通信(聊天室、MVC Controller) │
├──────────────────────────────────────────────────────────┤
│ 面试重点 │
│ • 手写观察者/发布订阅(EventEmitter) │
│ • 观察者 vs 发布订阅的区别 │
│ • 单例模式的多种实现 │
│ • 前端框架中用了哪些设计模式 │
└──────────────────────────────────────────────────────────┘