🧩 JavaScript 设计模式完全指南:从入门到精通(含20种模式)
前言:设计模式不是什么高深莫测的东西,它就是前人踩了无数坑之后总结出来的"套路"。就像做菜有菜谱,打篮球有战术,写代码也有设计模式。掌握它们,你的代码会变得更优雅、更好维护、更容易扩展。
本文用 生活化比喻 + 实际代码 + 源码分析 的方式讲解 20 种 常用的 JavaScript 设计模式,涵盖 创建型、结构型、行为型 三大类,适合有一定 JS 基础的开发者阅读。每种模式均配有 ES5 + ES6 两种写法 及 框架实战案例。
📖 目录
- 一、创建型模式(5种)
- [1. 单例模式(Singleton)](#1. 单例模式(Singleton))
- [2. 工厂模式(Factory)](#2. 工厂模式(Factory))
- [3. 抽象工厂模式(Abstract Factory)](#3. 抽象工厂模式(Abstract Factory))
- [4. 建造者模式(Builder)](#4. 建造者模式(Builder))
- [5. 原型模式(Prototype)](#5. 原型模式(Prototype))
- 二、结构型模式(7种)
- [6. 适配器模式(Adapter)](#6. 适配器模式(Adapter))
- [7. 装饰器模式(Decorator)](#7. 装饰器模式(Decorator))
- [8. 代理模式(Proxy)](#8. 代理模式(Proxy))
- [9. 外观模式(Facade)](#9. 外观模式(Facade))
- [10. 桥接模式(Bridge)](#10. 桥接模式(Bridge))
- [11. 组合模式(Composite)](#11. 组合模式(Composite))
- [12. 享元模式(Flyweight)](#12. 享元模式(Flyweight))
- 三、行为型模式(8种)
- [13. 观察者模式(Observer)](#13. 观察者模式(Observer))
- [14. 发布-订阅模式(Pub/Sub)](#14. 发布-订阅模式(Pub/Sub))
- [15. 策略模式(Strategy)](#15. 策略模式(Strategy))
- [16. 迭代器模式(Iterator)](#16. 迭代器模式(Iterator))
- [17. 模板方法模式(Template Method)](#17. 模板方法模式(Template Method))
- [18. 命令模式(Command)](#18. 命令模式(Command))
- [19. 职责链模式(Chain of Responsibility)](#19. 职责链模式(Chain of Responsibility))
- [20. 状态模式(State)](#20. 状态模式(State))
- 四、设计模式之间的关系
- 五、总结速查表
- 六、学习路线图
一、创建型模式
这类模式关注的是 "怎么创建对象",帮你把 new 的过程封装起来,让对象的创建更灵活、更可控。
1. 单例模式(Singleton)
🎯 一句话理解
一个类只能创建 一个实例,就像一个国家只有一个总统,一个学校只有一个校长。
🏠 生活类比
你家只有一个 WiFi 路由器,不管手机、电脑、平板怎么连接,都是连的同一个。不管你调用多少次 getInstance(),拿到的都是同一个对象。
💻 代码实现
ES5 写法:
javascript
var Singleton = (function () {
var instance = null;
function createInstance(config) {
return {
name: config.name,
getTime: function () {
return new Date().toLocaleString();
}
};
}
return {
getInstance: function (config) {
if (!instance) {
instance = createInstance(config);
}
return instance;
}
};
})();
var a = Singleton.getInstance({ name: '数据库连接' });
var b = Singleton.getInstance({ name: '另一个连接' });
console.log(a === b); // true ✅ 确实是同一个实例
console.log(a.name); // 数据库连接(第一次的配置)
ES6 写法:
javascript
class Singleton {
static #instance = null;
constructor(config) {
if (Singleton.#instance) {
return Singleton.#instance;
}
this.config = config;
this.createdAt = Date.now();
Singleton.#instance = this;
}
}
const a = new Singleton({ db: 'MySQL' });
const b = new Singleton({ db: 'PostgreSQL' }); // 无效,返回的仍然是a
console.log(a === b); // true
console.log(a.config.db); // MySQL
🛠️ 实际应用
1. Vuex / Redux 的 Store:整个应用只有一个数据仓库。
javascript
// Vue 项目中的全局弹窗管理器
class ModalManager {
static #instance = null;
#currentModal = null;
constructor() {
if (ModalManager.#instance) return ModalManager.#instance;
ModalManager.#instance = this;
}
open(component, props = {}) {
if (this.#currentModal) {
console.warn('已有弹窗打开,先关闭');
this.close();
}
this.#currentModal = { component, props };
console.log(`📦 弹窗打开: ${component.name || component}`);
}
close() {
if (this.#currentModal) {
console.log(`📦 弹窗关闭`);
this.#currentModal = null;
}
}
get isOpen() {
return this.#currentModal !== null;
}
}
// 不管在哪个组件里 new,拿到的都是同一个管理器
const m1 = new ModalManager();
const m2 = new ModalManager();
console.log(m1 === m2); // true
2. 数据库连接池 / WebSocket 连接:
javascript
class WebSocketManager {
static #instance = null;
#ws = null;
#listeners = [];
constructor(url) {
if (WebSocketManager.#instance) return WebSocketManager.#instance;
this.#ws = new WebSocket(url);
this.#ws.onmessage = (e) => {
this.#listeners.forEach(cb => cb(e.data));
};
WebSocketManager.#instance = this;
}
onMessage(cb) { this.#listeners.push(cb); }
send(data) { this.#ws.send(JSON.stringify(data)); }
}
⚠️ 注意事项
- 单例模式容易被滥用,只有真正需要全局唯一的场景才用
- 在测试中单例可能导致状态污染,需要注意 reset
- 多线程环境下需要加锁(JS 单线程无此问题)
2. 工厂模式(Factory)
🎯 一句话理解
你不需要知道产品怎么制造的,只需要告诉工厂 "我要什么",工厂帮你造出来。
🏠 生活类比
去餐厅点餐,你说"来份宫保鸡丁",后厨怎么做你不管,做好端上来就行。你就是调用者,后厨就是工厂。
💻 代码实现
简单工厂(Simple Factory):
javascript
class Dog {
constructor(name) {
this.name = name;
this.type = '狗';
this.sound = '汪汪!';
}
speak() { return `${this.name}: ${this.sound}`; }
ability() { return `${this.name}会看家`; }
}
class Cat {
constructor(name) {
this.name = name;
this.type = '猫';
this.sound = '喵喵~';
}
speak() { return `${this.name}: ${this.sound}`; }
ability() { return `${this.name}会抓老鼠`; }
}
class Parrot {
constructor(name) {
this.name = name;
this.type = '鹦鹉';
this.sound = '你好!';
}
speak() { return `${this.name}: ${this.sound}`; }
ability() { return `${this.name}会学说话`; }
}
// 简单工厂函数 ------ 你只需要传入类型
function AnimalFactory(type, name) {
const animalMap = {
dog: Dog,
cat: Cat,
parrot: Parrot,
};
const AnimalClass = animalMap[type];
if (!AnimalClass) throw new Error(`不支持的动物类型: ${type}`);
return new AnimalClass(name);
}
// 使用
const dog = AnimalFactory('dog', '旺财');
const cat = AnimalFactory('cat', '咪咪');
const parrot = AnimalFactory('parrot', '波利');
console.log(dog.speak()); // 旺财: 汪汪!
console.log(cat.ability()); // 咪咪会抓老鼠
console.log(parrot.speak()); // 波利: 你好!
工厂方法模式(Factory Method):
javascript
// 当简单工厂不够灵活时,让子类决定创建哪个对象
class AnimalFactory {
createAnimal(name) {
throw new Error('子类必须实现 createAnimal 方法');
}
}
class DogFactory extends AnimalFactory {
createAnimal(name) {
return new Dog(name);
}
}
class CatFactory extends AnimalFactory {
createAnimal(name) {
return new Cat(name);
}
}
// 使用
const dogFactory = new DogFactory();
const myDog = dogFactory.createAnimal('来福');
console.log(myDog.speak()); // 来福: 汪汪!
🛠️ 实际应用
React createElement:
javascript
// JSX 编译后的工厂函数
const element = React.createElement('div', { className: 'app' }, 'Hello');
// 等价于 JSX: <div className="app">Hello</div>
UI 组件库按钮工厂:
javascript
function createButton({ variant, size, text, onClick }) {
const button = document.createElement('button');
const variantStyles = {
primary: { backgroundColor: '#1890ff', color: '#fff' },
danger: { backgroundColor: '#ff4d4f', color: '#fff' },
ghost: { backgroundColor: 'transparent', color: '#1890ff', border: '1px solid #1890ff' },
text: { backgroundColor: 'transparent', color: '#333' },
};
const sizeStyles = {
small: { padding: '4px 12px', fontSize: '12px' },
medium: { padding: '8px 16px', fontSize: '14px' },
large: { padding: '12px 24px', fontSize: '16px' },
};
Object.assign(button.style, variantStyles[variant], sizeStyles[size]);
button.textContent = text;
button.onclick = onClick;
return button;
}
// 使用
document.body.appendChild(
createButton({
variant: 'primary',
size: 'large',
text: '提交',
onClick: () => console.log('clicked')
})
);
📊 简单工厂 vs 工厂方法
| 对比 | 简单工厂 | 工厂方法 |
|---|---|---|
| 创建逻辑 | 集中在一个函数 | 分散到各子类 |
| 扩展性 | 需要改工厂函数 | 新建工厂子类 |
| 适用场景 | 产品种类少 | 产品种类多且频繁扩展 |
3. 抽象工厂模式(Abstract Factory)
🎯 一句话理解
工厂的工厂。提供一个创建 一系列相关对象 的接口,而不指定具体类。
🏠 生活类比
你要装修房子,需要沙发、茶几、电视柜。你选择"北欧风套餐"或"中式风套餐",一套下来风格统一。不用自己去搭配。
💻 代码实现
javascript
// 产品族:按钮和输入框
class WindowsButton {
render() { return '🪟 Windows 风格按钮'; }
}
class WindowsInput {
render() { return '🪟 Windows 风格输入框'; }
}
class MacButton {
render() { return '🍎 Mac 风格按钮'; }
}
class MacInput {
render() { return '🍎 Mac 风格输入框'; }
}
// 抽象工厂
class UIFactory {
createButton() { throw new Error('必须实现'); }
createInput() { throw new Error('必须实现'); }
}
// 具体工厂:Windows
class WindowsFactory extends UIFactory {
createButton() { return new WindowsButton(); }
createInput() { return new WindowsInput(); }
}
// 具体工厂:Mac
class MacFactory extends UIFactory {
createButton() { return new MacButton(); }
createInput() { return new MacInput(); }
}
// 使用 ------ 根据平台选择工厂
function createUI(platform) {
const factoryMap = { windows: WindowsFactory, mac: MacFactory };
const Factory = factoryMap[platform];
const factory = new Factory();
return {
button: factory.createButton(),
input: factory.createInput()
};
}
const ui = createUI('mac');
console.log(ui.button.render()); // 🍎 Mac 风格按钮
console.log(ui.input.render()); // 🍎 Mac 风格输入框
📊 工厂模式三兄弟对比
| 模式 | 特点 | 类比 |
|---|---|---|
| 简单工厂 | 一个函数搞定所有 | 餐厅点菜 |
| 工厂方法 | 每个产品有自己的工厂 | 不同窗口取不同菜品 |
| 抽象工厂 | 一系列产品的统一工厂 | 整套装修方案 |
4. 建造者模式(Builder)
🎯 一句话理解
把一个 复杂的对象 拆成 一步一步来构建,像搭积木一样,每一步都可以自定义。
🏠 生活类比
你去电脑城组装电脑:先选 CPU,再选显卡,再选内存,最后选机箱。每一步你都可以自由搭配,最后组装成一台完整的电脑。
💻 代码实现
javascript
// 产品
class Computer {
constructor() {
this.parts = [];
}
add(part) {
this.parts.push(part);
return this;
}
show() {
console.log('💻 电脑配置清单:');
this.parts.forEach(p => console.log(` - ${p}`));
console.log(`💰 总价: ¥${this.calculatePrice()}`);
}
calculatePrice() {
const priceMap = {
'Intel i7-13700K': 2500, 'AMD Ryzen 9 7950X': 3500,
'RTX 4070': 4000, 'RTX 4090': 12000,
'32GB DDR5': 600, '64GB DDR5': 1200,
'1TB NVMe SSD': 500, '2TB NVMe SSD': 900,
};
return this.parts.reduce((sum, p) => sum + (priceMap[p] || 0), 0);
}
}
// 建造者
class ComputerBuilder {
#computer;
constructor() {
this.#computer = new Computer();
}
addCPU(cpu) {
this.#computer.add(`CPU: ${cpu}`);
return this; // 链式调用
}
addGPU(gpu) {
this.#computer.add(`显卡: ${gpu}`);
return this;
}
addRAM(size) {
this.#computer.add(`内存: ${size}`);
return this;
}
addStorage(storage) {
this.#computer.add(`硬盘: ${storage}`);
return this;
}
reset() {
this.#computer = new Computer();
return this;
}
build() {
const result = this.#computer;
this.#computer = new Computer();
return result;
}
}
// 使用 ------ 链式调用,清晰优雅
const myPC = new ComputerBuilder()
.addCPU('Intel i7-13700K')
.addGPU('RTX 4070')
.addRAM('32GB DDR5')
.addStorage('1TB NVMe SSD')
.build();
myPC.show();
// 💻 电脑配置清单:
// - CPU: Intel i7-13700K
// - 显卡: RTX 4070
// - 内存: 32GB DDR5
// - 硬盘: 1TB NVMe SSD
// 💰 总价: ¥7600
🛠️ 实际应用
SQL 查询构建器:
javascript
class QueryBuilder {
#table = '';
#fields = ['*'];
#conditions = [];
#orderField = '';
#orderDir = 'ASC';
#limitVal = 0;
table(name) { this.#table = name; return this; }
select(...fields) { this.#fields = fields; return this; }
where(condition) { this.#conditions.push(condition); return this; }
orderBy(field, dir = 'ASC') { this.#orderField = field; this.#orderDir = dir; return this; }
limit(n) { this.#limitVal = n; return this; }
build() {
let sql = `SELECT ${this.#fields.join(', ')} FROM ${this.#table}`;
if (this.#conditions.length) sql += ` WHERE ${this.#conditions.join(' AND ')}`;
if (this.#orderField) sql += ` ORDER BY ${this.#orderField} ${this.#orderDir}`;
if (this.#limitVal) sql += ` LIMIT ${this.#limitVal}`;
return sql;
}
}
const sql = new QueryBuilder()
.table('users')
.select('id', 'name', 'email')
.where('age > 18')
.where('status = "active"')
.orderBy('created_at', 'DESC')
.limit(10)
.build();
console.log(sql);
// SELECT id, name, email FROM users WHERE age > 18 AND status = "active" ORDER BY created_at DESC LIMIT 10
axios 请求构建器:
javascript
class RequestBuilder {
#config = { method: 'GET', headers: {}, timeout: 5000 };
setMethod(method) { this.#config.method = method; return this; }
setURL(url) { this.#config.url = url; return this; }
setHeader(key, value) { this.#config.headers[key] = value; return this; }
setBody(data) { this.#config.data = data; return this; }
setTimeout(ms) { this.#config.timeout = ms; return this; }
async execute() {
return axios(this.#config);
}
}
// 使用
const response = await new RequestBuilder()
.setMethod('POST')
.setURL('/api/login')
.setHeader('Content-Type', 'application/json')
.setBody({ username: 'admin', password: '123' })
.setTimeout(10000)
.execute();
5. 原型模式(Prototype)
🎯 一句话理解
通过 克隆 一个已有对象来创建新对象,而不是从头 new。利用 JS 原型链的特性,这是最自然的模式。
🏠 生活类比
就像复印文件------你有一份模板,直接复印就行,不用重新打字。或者像细胞分裂,一个变两个,两个变四个。
💻 代码实现
javascript
// 深拷贝实现原型克隆
function deepClone(obj, cache = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (cache.has(obj)) return cache.get(obj);
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (Array.isArray(obj)) return obj.map(item => deepClone(item, cache));
const clone = Object.create(Object.getPrototypeOf(obj));
cache.set(obj, clone);
for (const key of Reflect.ownKeys(obj)) {
clone[key] = deepClone(obj[key], cache);
}
return clone;
}
// 使用
const templateUser = {
name: '模板用户',
role: 'user',
permissions: ['read', 'write'],
settings: { theme: 'dark', lang: 'zh' }
};
const user1 = deepClone(templateUser);
user1.name = '张三';
user1.settings.theme = 'light';
const user2 = deepClone(templateUser);
user2.name = '李四';
user2.permissions.push('delete');
console.log(user1.settings.theme); // light(互不影响)
console.log(user2.settings.theme); // dark(保持原始值)
console.log(templateUser.settings.theme); // dark(模板不变)
javascript
// Object.create 实现原型继承
const animalPrototype = {
type: '未知',
speak() { return `${this.name}: ${this.sound}(我是${this.type})`; },
clone() { return Object.create(this); }
};
const dog = animalPrototype.clone();
dog.name = '旺财'; dog.type = '狗'; dog.sound = '汪汪!';
console.log(dog.speak()); // 旺财: 汪汪!(我是狗)
🛠️ 实际应用
JS 的整个原型继承体系就是原型模式的实现。Object.create()、__proto__、class extends 底层都是原型链操作。
javascript
// Vue 2 中的 VNode 克隆
function cloneVNode(vnode) {
const cloned = new VNode(
vnode.tag, vnode.data, vnode.children,
vnode.text, vnode.elm, vnode.context,
vnode.componentOptions
);
return cloned;
}
二、结构型模式
这类模式关注的是 "怎么把对象组合起来",帮你灵活地搭建代码结构,让不同模块协同工作。
6. 适配器模式(Adapter)
🎯 一句话理解
让 不兼容的接口 能够协同工作,就像万能转换插头。
🏠 生活类比
你去国外旅游,带的中国电器插头是扁的,但当地插座是圆的。怎么办?买个转换插头就行了。这个转换插头就是适配器。
💻 代码实现
javascript
// 场景:对接第三方地图 SDK
// 高德地图接口
const gaodeMap = {
show(lat, lng) { console.log(`高德地图: (${lat}, ${lng})`); },
getRoute(from, to) { return `高德路线: ${from} → ${to}`; }
};
// 百度地图接口(参数格式不同)
const baiduMap = {
display(coordinates) { console.log(`百度地图: ${JSON.stringify(coordinates)}`); },
navigation(start, end) { return `百度路线: ${start} → ${end}`; }
};
// 统一接口适配器
class MapAdapter {
constructor(mapSDK) { this.map = mapSDK; }
showPoint(lat, lng) {
if (this.map.show) this.map.show(lat, lng);
else if (this.map.display) this.map.display({ lat, lng });
}
getRoute(from, to) {
if (this.map.getRoute) return this.map.getRoute(from, to);
else if (this.map.navigation) return this.map.navigation(from, to);
}
}
// 使用 ------ 统一接口,不关心底层是哪个地图
const adapter1 = new MapAdapter(gaodeMap);
const adapter2 = new MapAdapter(baiduMap);
adapter1.showPoint(39.9, 116.3); // 高德地图: (39.9, 116.3)
adapter2.showPoint(39.9, 116.3); // 百度地图: {"lat":39.9,"lng":116.3}
🛠️ 实际应用
前后端数据格式适配:
javascript
const apiResponse = {
order_id: 1001,
create_time: '2026-05-18T10:30:00Z',
total_amount: 299.9,
pay_status: 1,
user_info: { u_name: '张三', u_phone: '13800138000' }
};
function OrderAdapter(data) {
const payStatusMap = { 0: '未支付', 1: '已支付', 2: '已取消', 3: '已退款' };
return {
orderId: data.order_id,
createTime: new Date(data.create_time).toLocaleString('zh-CN'),
totalAmount: `¥${data.total_amount.toFixed(2)}`,
payStatus: payStatusMap[data.pay_status],
userName: data.user_info.u_name,
userPhone: data.user_info.u_phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
};
}
const order = OrderAdapter(apiResponse);
console.log(order);
// { orderId: 1001, createTime: '2026/5/18 18:30:00', totalAmount: '¥299.90',
// payStatus: '已支付', userName: '张三', userPhone: '138****8000' }
7. 装饰器模式(Decorator)
🎯 一句话理解
不修改原对象,给它动态添加新功能,像给手机套壳、贴膜、挂件一样,层层增强。
🏠 生活类比
你买了一部手机,它本身能打电话。你可以给它套个壳(防摔功能)、贴个膜(防刮功能)、挂个手绳(防丢功能)。手机本身没变,但功能一步步增强了。
💻 代码实现
javascript
// 基础咖啡
class Coffee {
cost() { return 10; }
description() { return '☕ 黑咖啡'; }
}
// 装饰器基类
class CoffeeDecorator {
constructor(coffee) { this.coffee = coffee; }
cost() { return this.coffee.cost(); }
description() { return this.coffee.description(); }
}
// 具体装饰器
class MilkDecorator extends CoffeeDecorator {
cost() { return this.coffee.cost() + 3; }
description() { return this.coffee.description() + ' + 🥛牛奶'; }
}
class MochaDecorator extends CoffeeDecorator {
cost() { return this.coffee.cost() + 5; }
description() { return this.coffee.description() + ' + 🍫摩卡'; }
}
class WhipDecorator extends CoffeeDecorator {
cost() { return this.coffee.cost() + 2; }
description() { return this.coffee.description() + ' + 🫧奶泡'; }
}
// 自由组合 ------ 像搭积木一样
let myCoffee = new Coffee();
myCoffee = new MilkDecorator(myCoffee);
myCoffee = new MochaDecorator(myCoffee);
myCoffee = new WhipDecorator(myCoffee);
console.log(myCoffee.description()); // ☕ 黑咖啡 + 🥛牛奶 + 🍫摩卡 + 🫧奶泡
console.log(`价格: ¥${myCoffee.cost()}`); // 价格: ¥20
函数装饰器(AOP 风格):
javascript
// 通用函数装饰器
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 withTiming(fn) {
return function (...args) {
const start = performance.now();
const result = fn.apply(this, args);
console.log(`⏱ ${fn.name} 耗时: ${(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) {
console.warn(`🔄 第${i + 1}次重试: ${err.message}`);
if (i === maxRetries - 1) throw err;
await new Promise(r => setTimeout(r, 1000 * (i + 1)));
}
}
};
}
// 使用 ------ 组合装饰器
async function fetchUserData(userId) {
const res = await fetch(`/api/user/${userId}`);
return res.json();
}
const enhancedFetch = withRetry(withLogging(fetchUserData), 3);
🛠️ 实际应用
React 高阶组件(HOC):
javascript
function withAuth(WrappedComponent) {
return function AuthComponent(props) {
const token = localStorage.getItem('token');
if (!token) return <Navigate to="/login" />;
return <WrappedComponent {...props} />;
};
}
function withLoading(WrappedComponent) {
return function LoadingComponent({ isLoading, ...props }) {
if (isLoading) return <Spinner />;
return <WrappedComponent {...props} />;
};
}
// 组合使用
const EnhancedPage = withLoading(withAuth(Dashboard));
8. 代理模式(Proxy)
🎯 一句话理解
给对象找个 "代理",在访问对象之前加一层控制。和装饰器的区别是:代理控制访问,装饰器增强功能。
🏠 生活类比
明星的经纪人。你想找明星代言,不是直接联系明星,而是先联系经纪人。经纪人会帮你筛选、谈价格、安排时间。经纪人就是代理。
💻 代码实现
javascript
const user = {
name: '张三',
age: 25,
salary: 20000,
password: 'abc123'
};
const userProxy = new Proxy(user, {
// 拦截读取
get(target, key) {
if (key === 'salary') {
const isHR = localStorage.getItem('role') === 'hr';
if (!isHR) {
console.warn('⚠ 薪资信息需要HR权限');
return '***';
}
}
if (key === 'password') {
console.warn('⚠ 密码字段不可读取');
return undefined;
}
return target[key];
},
// 拦截赋值
set(target, key, value) {
if (key === 'age' && (value < 0 || value > 150)) {
throw new Error('年龄不合法');
}
if (key === 'password' && value.length < 6) {
throw new Error('密码至少6位');
}
console.log(`📝 ${key} 已更新: ${target[key]} → ${value}`);
target[key] = value;
return true;
}
});
console.log(userProxy.name); // 张三
console.log(userProxy.salary); // ⚠ 薪资信息需要HR权限 → '***'
缓存代理(性能优化利器):
javascript
function createCacheProxy(fn) {
const cache = new Map();
return new Proxy(fn, {
apply(target, thisArg, args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log(`🎯 缓存命中: ${key}`);
return cache.get(key);
}
const result = target.apply(thisArg, args);
cache.set(key, result);
return result;
}
});
}
// 昂贵的计算函数
const fastFib = createCacheProxy(function fib(n) {
console.log(`💻 计算中... fib(${n})`);
if (n <= 1) return n;
return fastFib(n - 1) + fastFib(n - 2);
});
console.log(fastFib(10)); // 第一次计算
console.log(fastFib(10)); // 🎯 缓存命中
🛠️ 实际应用
Vue 3 的 reactive 实现(简化版):
javascript
const targetMap = new WeakMap();
let activeEffect = null;
function reactive(target) {
return new Proxy(target, {
get(obj, key) {
track(obj, key);
const result = obj[key];
return typeof result === 'object' ? reactive(result) : result;
},
set(obj, key, value) {
const oldValue = obj[key];
obj[key] = value;
if (oldValue !== value) trigger(obj, key);
return true;
}
});
}
📊 代理 vs 装饰器
| 对比 | 代理模式 | 装饰器模式 |
|---|---|---|
| 目的 | 控制访问 | 增强功能 |
| 是否改变接口 | 不变 | 可扩展 |
| 创建方式 | 代理控制对象创建 | 装饰器包裹已有对象 |
9. 外观模式(Facade)
🎯 一句话理解
把 复杂的子系统 封装成一个 简单的接口,对外只暴露一个入口。让调用者不用关心内部复杂性。
🏠 生活类比
你在家看电影,需要:开电视 → 切换HDMI → 开音响 → 调音量 → 关灯 → 拉窗帘。每次都要操作6个遥控器太麻烦。买个"智能家居"面板,一键"观影模式"搞定。这个面板就是外观模式。
💻 代码实现
javascript
// 复杂的子系统
class TV {
on() { console.log('📺 电视打开'); }
off() { console.log('📺 电视关闭'); }
setSource(src) { console.log(`📺 切换到 ${src}`); }
}
class SoundSystem {
on() { console.log('🔊 音响打开'); }
off() { console.log('🔊 音响关闭'); }
setVolume(v) { console.log(`🔊 音量: ${v}`); }
}
class Lights {
setLevel(level) { console.log(`💡 灯光亮度: ${level}%`); }
}
class Curtains {
open() { console.log('🪟 窗帘打开'); }
close() { console.log('🪟 窗帘关闭'); }
}
// 外观 ------ 一键操作
class HomeFacade {
constructor() {
this.tv = new TV();
this.sound = new SoundSystem();
this.lights = new Lights();
this.curtains = new Curtains();
}
watchMovie() {
console.log('🎬 开启观影模式...');
this.curtains.close();
this.lights.setLevel(20);
this.tv.on();
this.tv.setSource('HDMI');
this.sound.on();
this.sound.setVolume(50);
}
endMovie() {
console.log('🎬 结束观影模式...');
this.tv.off();
this.sound.off();
this.lights.setLevel(100);
this.curtains.open();
}
sleepMode() {
console.log('🌙 睡眠模式...');
this.tv.off();
this.sound.off();
this.lights.setLevel(0);
this.curtains.close();
}
}
// 使用 ------ 简单!
const home = new HomeFacade();
home.watchMovie();
// 🪟 窗帘关闭 → 💡 灯光亮度: 20% → 📺 电视打开 → 📺 切换到 HDMI → 🔊 音响打开 → 🔊 音量: 50
🛠️ 实际应用
项目中的通用 API 模块:
javascript
class ApiFacade {
constructor(baseURL) {
this.baseURL = baseURL;
this.token = localStorage.getItem('token');
}
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const headers = {
'Content-Type': 'application/json',
...(this.token ? { Authorization: `Bearer ${this.token}` } : {}),
...options.headers
};
try {
const res = await fetch(url, { ...options, headers });
if (res.status === 401) {
this.token = null;
localStorage.removeItem('token');
window.location.href = '/login';
return;
}
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
} catch (err) {
console.error(`API Error: ${err.message}`);
throw err;
}
}
get(endpoint) { return this.request(endpoint); }
post(endpoint, data) { return this.request(endpoint, { method: 'POST', body: JSON.stringify(data) }); }
put(endpoint, data) { return this.request(endpoint, { method: 'PUT', body: JSON.stringify(data) }); }
delete(endpoint) { return this.request(endpoint, { method: 'DELETE' }); }
login(username, password) {
return this.post('/auth/login', { username, password }).then(data => {
this.token = data.token;
localStorage.setItem('token', data.token);
return data;
});
}
getUser(id) { return this.get(`/users/${id}`); }
getArticles(page = 1) { return this.get(`/articles?page=${page}`); }
}
const api = new ApiFacade('https://api.example.com');
10. 桥接模式(Bridge)
🎯 一句话理解
将 抽象部分 和 实现部分 分离,使它们可以独立变化。避免多层继承导致的类爆炸。
🏠 生活类比
遥控器和电视是分开的。换遥控器不用换电视,换电视不用换遥控器。它们通过红外信号(桥接)通信。
💻 代码实现
javascript
// 实现部分:颜色
class Color {
apply() { throw new Error('必须实现'); }
}
class Red extends Color { apply() { return '红色'; } }
class Blue extends Color { apply() { return '蓝色'; } }
class Green extends Color { apply() { return '绿色'; } }
// 抽象部分:形状
class Shape {
constructor(color) { this.color = color; }
draw() { throw new Error('必须实现'); }
}
class Circle extends Shape {
draw() { console.log(`⭕ 画一个${this.color.apply()}的圆`); }
}
class Square extends Shape {
draw() { console.log(`⬜ 画一个${this.color.apply()}的正方形`); }
}
class Triangle extends Shape {
draw() { console.log(`🔺 画一个${this.color.apply()}的三角形`); }
}
// 使用 ------ 形状和颜色自由组合
new Circle(new Red()).draw(); // ⭕ 画一个红色的圆
new Square(new Blue()).draw(); // ⬜ 画一个蓝色的正方形
new Triangle(new Green()).draw(); // 🔺 画一个绿色的三角形
// 新增颜色?加一个类就行,不用改任何形状代码
class Gradient extends Color { apply() { return '渐变色'; } }
new Circle(new Gradient()).draw(); // ⭕ 画一个渐变色的圆
更实际的例子 ------ 消息发送系统:
javascript
// 实现部分:发送渠道
class EmailSender {
send(to, message) { console.log(`📧 发邮件给 ${to}: ${message}`); }
}
class SmsSender {
send(to, message) { console.log(`📱 发短信给 ${to}: ${message}`); }
}
class WechatSender {
send(to, message) { console.log(`💬 发微信给 ${to}: ${message}`); }
}
// 抽象部分:消息类型
class Message {
constructor(sender) { this.sender = sender; }
deliver(to, content) { throw new Error('必须实现'); }
}
class AlertMessage extends Message {
deliver(to, content) { this.sender.send(to, `🚨 警报: ${content}`); }
}
class PromoMessage extends Message {
deliver(to, content) { this.sender.send(to, `🎉 优惠: ${content}`); }
}
// 组合使用 ------ 3种渠道 × 2种类型 = 6种组合,只需5个类
new AlertMessage(new EmailSender()).deliver('admin@test.com', 'CPU占用90%');
new PromoMessage(new SmsSender()).deliver('13800138000', '全场5折');
11. 组合模式(Composite)
🎯 一句话理解
把对象组合成 树形结构 ,让用户对单个对象和组合对象的使用具有 一致性。
🏠 生活类比
文件系统:文件夹里面可以放文件,也可以放子文件夹。你不管是双击文件还是双击文件夹,都是"打开"操作。操作系统把文件和文件夹统一对待了。
💻 代码实现
javascript
// 统一接口
class FileSystemComponent {
constructor(name) { this.name = name; }
display(indent = '') { throw new Error('必须实现'); }
getSize() { throw new Error('必须实现'); }
}
// 叶子节点:文件
class File extends FileSystemComponent {
constructor(name, size) { super(name); this.size = size; }
display(indent = '') { console.log(`${indent}📄 ${this.name} (${this.size}KB)`); }
getSize() { return this.size; }
}
// 组合节点:文件夹
class Folder extends FileSystemComponent {
constructor(name) { super(name); this.children = []; }
add(component) { this.children.push(component); return this; }
remove(component) { this.children = this.children.filter(c => c !== component); }
display(indent = '') {
console.log(`${indent}📁 ${this.name}/`);
this.children.forEach(child => child.display(indent + ' '));
}
getSize() {
return this.children.reduce((sum, child) => sum + child.getSize(), 0);
}
}
// 构建文件树
const root = new Folder('项目');
const src = new Folder('src');
const components = new Folder('components');
root.add(src);
src.add(components);
components.add(new File('Header.vue', 5));
components.add(new File('Footer.vue', 3));
root.add(new File('package.json', 1));
root.display();
// 📁 项目/
// 📁 src/
// 📁 components/
// 📄 Header.vue (5KB)
// 📄 Footer.vue (3KB)
// 📄 package.json (1KB)
console.log(`总大小: ${root.getSize()}KB`); // 总大小: 9KB
🛠️ 实际应用
DOM 树结构 、Vue/React 组件树 都是组合模式的体现。每个组件可以包含子组件,形成树形结构,统一使用相同的接口。
12. 享元模式(Flyweight)
🎯 一句话理解
通过 共享 相同的数据来 减少内存使用,把公共的部分提取出来,只保留一份。
🏠 生活类比
咖啡店有100个客人,每个人都要一杯拿铁。咖啡店不需要准备100份拿铁配方,只需要1份配方 + 100个杯子。配方就是享元(共享的),杯子就是外部状态(每个客人独有的)。
💻 代码实现
javascript
// 享元工厂 ------ 共享字体对象
class FontFactory {
static #fonts = new Map();
static getFont(family, size, color, bold = false, italic = false) {
const key = `${family}|${size}|${color}|${bold}|${italic}`;
if (!this.#fonts.has(key)) {
this.#fonts.set(key, { family, size, color, bold, italic });
console.log(`🎨 创建新字体: ${key}`);
}
return this.#fonts.get(key);
}
static getFontCount() { return this.#fonts.size; }
}
// 字符类 ------ 只存储位置信息,字体共享
class Character {
constructor(char, x, y, font) {
this.char = char; // 外部状态
this.x = x;
this.y = y;
this.font = font; // 内部状态:共享的!
}
render() {
return `[${this.font.family} ${this.font.size}px] ${this.char} at (${this.x}, ${this.y})`;
}
}
// 10万个字符,但只有几种字体
const chars = [];
const text = '这是一段示例文本用来演示享元模式';
for (let i = 0; i < 100000; i++) {
const char = text[i % text.length];
const font = FontFactory.getFont(
i % 3 === 0 ? '宋体' : i % 3 === 1 ? '微软雅黑' : 'Arial',
i % 2 === 0 ? 14 : 16,
i % 4 === 0 ? '#333' : '#666',
i % 5 === 0,
i % 7 === 0
);
chars.push(new Character(char, i * 10, 0, font));
}
console.log(`字符数: ${chars.length}`); // 100000
console.log(`字体对象数: ${FontFactory.getFontCount()}`); // 远小于10万!
// 不用享元:10万个字体对象
// 用了享元:10万字符共享少量字体对象
列表虚拟化也是享元思想:
javascript
class VirtualList {
constructor(container, items, itemHeight = 40) {
this.container = container;
this.items = items;
this.itemHeight = itemHeight;
this.visibleCount = Math.ceil(container.clientHeight / itemHeight) + 2;
this.pool = []; // DOM 节点池(享元)
for (let i = 0; i < this.visibleCount; i++) {
const div = document.createElement('div');
div.style.height = `${itemHeight}px`;
this.pool.push(div);
container.appendChild(div);
}
container.addEventListener('scroll', () => this.render());
this.render();
}
render() {
const scrollTop = this.container.scrollTop;
const startIndex = Math.floor(scrollTop / this.itemHeight);
this.pool.forEach((div, i) => {
const index = startIndex + i;
if (index < this.items.length) {
div.textContent = this.items[index];
div.style.transform = `translateY(${index * this.itemHeight - scrollTop}px)`;
}
});
}
}
// 10万条数据,但只创建 ~20 个 DOM 节点
三、行为型模式
这类模式关注的是 "对象之间怎么通信",帮你组织代码的执行流程,让逻辑更清晰、更灵活。
13. 观察者模式(Observer)
🎯 一句话理解
一个对象状态变化时,自动通知 所有依赖它的对象。像班主任通知全班同学一样。
🏠 生活类比
班主任在班级群里发通知:"明天考试!" 所有同学(观察者)都会收到消息。老师不需要一个个私聊,发一次全员可见。
💻 代码实现
javascript
// 被观察者(Subject)
class Subject {
#observers = [];
#state = null;
subscribe(observer) {
if (!this.#observers.includes(observer)) {
this.#observers.push(observer);
console.log(`✅ ${observer.name} 已订阅`);
}
return this;
}
unsubscribe(observer) {
this.#observers = this.#observers.filter(o => o !== observer);
console.log(`❌ ${observer.name} 已取消订阅`);
return this;
}
setState(newState) {
const oldState = this.#state;
this.#state = newState;
console.log(`📢 状态更新: ${JSON.stringify(oldState)} → ${JSON.stringify(newState)}`);
this.#observers.forEach(o => o.update(newState, oldState));
}
}
// 观察者(Observer)
class Observer {
constructor(name, callback) {
this.name = name;
this.callback = callback;
}
update(newState, oldState) {
this.callback(newState, oldState);
}
}
// 使用 ------ 气象站通知多个显示屏
const weatherStation = new Subject();
const phoneDisplay = new Observer('手机屏', (state) => {
console.log(`📱 手机: 温度 ${state.temperature}°C, 湿度 ${state.humidity}%`);
});
const alarmSystem = new Observer('报警系统', (state) => {
if (state.temperature > 35) console.log(`🚨 高温警报: ${state.temperature}°C!`);
});
weatherStation.subscribe(phoneDisplay).subscribe(alarmSystem);
weatherStation.setState({ temperature: 28, humidity: 65, weather: '晴' });
// 📱 手机: 温度 28°C, 湿度 65%
weatherStation.setState({ temperature: 38, humidity: 40, weather: '热' });
// 📱 手机: 温度 38°C, 湿度 40%
// 🚨 高温警报: 38°C!
weatherStation.unsubscribe(phoneDisplay);
weatherStation.setState({ temperature: 25, humidity: 70, weather: '多云' });
// 手机屏不再收到通知
🛠️ 实际应用
Vue 的响应式系统、RxJS、Node.js 的 EventEmitter 都是观察者模式的实现。
14. 发布-订阅模式(Pub/Sub)
🎯 一句话理解
和观察者模式的区别:发布者和订阅者 互不认识 ,通过一个 消息中心 间接通信。像微信公众号一样。
🏠 生活类比
观察者模式:老师直接通知学生(知道学生是谁)。发布-订阅模式:你关注了公众号"前端早读课",编辑发文章你就能收到。编辑不知道你是谁,你也不知道编辑是谁,中间人是微信平台。
💻 代码实现
javascript
// 消息中心(Event Bus)
class EventBus {
static #instance = null;
#events = {};
constructor() {
if (EventBus.#instance) return EventBus.#instance;
EventBus.#instance = this;
}
// 订阅
on(event, callback) {
if (!this.#events[event]) this.#events[event] = [];
this.#events[event].push(callback);
return () => this.off(event, callback); // 返回取消订阅函数
}
// 只订阅一次
once(event, callback) {
const wrapper = (...args) => {
callback(...args);
this.off(event, wrapper);
};
this.on(event, wrapper);
}
// 取消订阅
off(event, callback) {
if (!this.#events[event]) return;
this.#events[event] = this.#events[event].filter(cb => cb !== callback);
}
// 发布
emit(event, ...args) {
if (!this.#events[event]) return;
this.#events[event].forEach(cb => cb(...args));
}
clear() { this.#events = {}; }
}
// 使用 ------ 跨组件通信
const bus = new EventBus();
// 组件A:监听用户登录
const unsub = bus.on('user:login', (user) => {
console.log(`📊 数据面板: 欢迎 ${user.name}`);
});
bus.on('user:login', (user) => {
console.log(`🔔 通知栏: ${user.name} 上线了`);
});
// 组件B:触发登录
bus.emit('user:login', { name: '张三', role: 'admin' });
// 📊 数据面板: 欢迎 张三
// 🔔 通知栏: 张三 上线了
// 取消订阅
unsub();
📊 观察者 vs 发布-订阅
| 对比 | 观察者模式 | 发布-订阅模式 |
|---|---|---|
| 通信方式 | 直接通信 | 通过消息中心间接通信 |
| 耦合度 | 较高 | 很低(互不知道) |
| 可扩展性 | 一般 | 很强 |
| 代表实现 | Vue 响应式、RxJS | EventBus、Redux、MQ |
15. 策略模式(Strategy)
🎯 一句话理解
把一系列 算法 封装起来,让它们可以互相替换。消除 if-else 地狱。
🏠 生活类比
你从北京去上海,可以坐飞机、高铁、自驾、大巴。每种交通方式就是一个"策略"。你不用关心每种方式的具体细节,选择一种就行。
💻 代码实现
javascript
// 策略集合
const pricingStrategies = {
normal: {
calculate(price) { return price; },
getLabel() { return '普通价'; }
},
silver: {
calculate(price) { return price * 0.9; },
getLabel() { return '银牌9折'; }
},
gold: {
calculate(price) {
let finalPrice = price * 0.8;
if (finalPrice >= 200) finalPrice -= 20;
return finalPrice;
},
getLabel() { return '金牌8折+满减'; }
},
diamond: {
calculate(price) { return Math.max(0, price * 0.7 - 30); },
getLabel() { return '钻石7折+立减'; }
}
};
// 价格计算器(Context)
class PriceCalculator {
constructor(strategy = 'normal') { this.setStrategy(strategy); }
setStrategy(strategy) {
if (!pricingStrategies[strategy]) throw new Error(`未知策略: ${strategy}`);
this.strategy = pricingStrategies[strategy];
console.log(`🔄 切换到: ${this.strategy.getLabel()}`);
return this;
}
calculate(price) {
const finalPrice = this.strategy.calculate(price);
console.log(`💰 原价: ¥${price} → ${this.strategy.getLabel()}: ¥${finalPrice.toFixed(2)}`);
return finalPrice;
}
}
const calc = new PriceCalculator('normal');
calc.calculate(300); // ¥300.00
calc.setStrategy('silver').calculate(300); // ¥270.00
calc.setStrategy('gold').calculate(300); // ¥220.00
calc.setStrategy('diamond').calculate(300); // ¥180.00
表单验证策略:
javascript
const validators = {
required: (value, msg = '必填') => value.trim() ? null : msg,
minLength: (value, min, msg) => value.length >= min ? null : (msg || `最少${min}个字符`),
email: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) ? null : '邮箱格式不正确',
phone: (value) => /^1[3-9]\d{9}$/.test(value) ? null : '手机号格式不正确',
};
class FormValidator {
#rules = [];
addRule(field, value, ...strategies) {
this.#rules.push({ field, value, strategies });
return this;
}
validate() {
const errors = {};
for (const rule of this.#rules) {
for (const [name, ...args] of rule.strategies) {
const error = validators[name](rule.value, ...args);
if (error) {
if (!errors[rule.field]) errors[rule.field] = [];
errors[rule.field].push(error);
break;
}
}
}
return Object.keys(errors).length ? errors : null;
}
}
const v = new FormValidator();
v.addRule('username', 'ab', ['required'], ['minLength', 3, '用户名至少3个字符'])
.addRule('email', 'bad', ['required'], ['email']);
console.log(v.validate());
// { username: ['用户名至少3个字符'], email: ['邮箱格式不正确'] }
16. 迭代器模式(Iterator)
🎯 一句话理解
提供一种方法 顺序访问 集合中的元素,而不暴露集合的内部结构。
🏠 生活类比
自助餐厅的传送带。你不需要知道厨房是怎么摆盘的,只需要等传送带把菜一道一道送到你面前。
💻 代码实现
javascript
// 自定义可迭代对象
class NumberRange {
constructor(start, end, step = 1) {
this.start = start; this.end = end; this.step = step;
}
[Symbol.iterator]() {
let current = this.start;
const self = this;
return {
next() {
if (current <= self.end) {
const value = current;
current += self.step;
return { value, done: false };
}
return { value: undefined, done: true };
}
};
}
}
// 可以用 for...of 遍历!
for (const num of new NumberRange(1, 10, 2)) {
console.log(num); // 1, 3, 5, 7, 9
}
console.log([...new NumberRange(0, 5)]); // [0, 1, 2, 3, 4, 5]
二叉树遍历迭代器:
javascript
class TreeNode {
constructor(value, left = null, right = null) {
this.value = value; this.left = left; this.right = right;
}
}
class BinaryTree {
constructor(root) { this.root = root; }
// 中序遍历(左 → 根 → 右)------ 升序输出
*[Symbol.iterator]() {
function* inorder(node) {
if (!node) return;
yield* inorder(node.left);
yield node.value;
yield* inorder(node.right);
}
yield* inorder(this.root);
}
// 前序遍历
*preorder() {
function* pre(node) {
if (!node) return;
yield node.value;
yield* pre(node.left);
yield* pre(node.right);
}
yield* pre(this.root);
}
}
const tree = new BinaryTree(
new TreeNode(4,
new TreeNode(2, new TreeNode(1), new TreeNode(3)),
new TreeNode(6, new TreeNode(5), new TreeNode(7))
)
);
console.log([...tree]); // [1, 2, 3, 4, 5, 6, 7]
console.log([...tree.preorder()]); // [4, 2, 1, 3, 6, 5, 7]
17. 模板方法模式(Template Method)
🎯 一句话理解
在父类中定义 算法的骨架,把某些步骤延迟到子类实现。子类可以重写细节,但不能改变整体流程。
🏠 生活类比
做菜有一个固定流程:备料 → 下锅 → 调味 → 装盘。这个流程是固定的(模板),但每道菜的具体做法不同。炒青菜和红烧肉都走同样的步骤,但具体内容大不相同。
💻 代码实现
javascript
// 模板类:数据解析器
class DataParser {
// 模板方法 ------ 定义算法骨架
parse(input) {
console.log('--- 开始解析 ---');
const raw = this.read(input);
const validated = this.validate(raw);
const processed = this.process(validated);
const result = this.format(processed);
console.log('--- 解析完成 ---');
return result;
}
// 具体步骤 ------ 子类实现
read(input) { throw new Error('必须实现 read'); }
validate(data) {
if (!data) throw new Error('数据为空');
console.log('✅ 验证通过');
return data;
}
process(data) { throw new Error('必须实现 process'); }
format(data) {
console.log('📋 格式化输出');
return JSON.stringify(data, null, 2);
}
}
// CSV 解析器
class CsvParser extends DataParser {
read(input) {
console.log('📄 读取 CSV');
return input.split('\n').map(line => line.split(','));
}
process(data) {
const headers = data[0];
return data.slice(1).map(row => {
const obj = {};
headers.forEach((h, i) => obj[h.trim()] = row[i]?.trim());
return obj;
});
}
}
// JSON 解析器
class JsonParser extends DataParser {
read(input) { return JSON.parse(input); }
validate(data) {
super.validate(data);
if (!Array.isArray(data)) throw new Error('必须是数组');
return data;
}
process(data) { return data.filter(item => item.active !== false); }
}
// 使用 ------ 流程固定,实现不同
const csv = new CsvParser();
csv.parse('name,age\n张三,25\n李四,30');
18. 命令模式(Command)
🎯 一句话理解
把 请求 封装成对象,使你可以参数化、排队、记录日志、支持 撤销操作。
🏠 生活类比
餐厅点餐:你写一张菜单(命令),交给服务员(调用者),服务员传给后厨(接收者)。你和后厨没直接接触,中间通过"命令"传达。而且你可以取消订单(撤销)。
💻 代码实现
javascript
// 接收者
class Light { on() { console.log('💡 灯开了'); } off() { console.log('💡 灯关了'); } }
class TV { on() { console.log('📺 电视开了'); } off() { console.log('📺 电视关了'); } }
// 命令接口
class Command {
execute() { throw new Error('必须实现'); }
undo() { throw new Error('必须实现'); }
}
class LightOnCommand extends Command {
constructor(light) { super(); this.light = light; }
execute() { this.light.on(); }
undo() { this.light.off(); }
}
class TVOnCommand extends Command {
constructor(tv) { super(); this.tv = tv; }
execute() { this.tv.on(); }
undo() { this.tv.off(); }
}
// 宏命令:一键执行多个命令
class MacroCommand extends Command {
#commands = [];
add(cmd) { this.#commands.push(cmd); return this; }
execute() { this.#commands.forEach(cmd => cmd.execute()); }
undo() { [...this.#commands].reverse().forEach(cmd => cmd.undo()); }
}
// 遥控器(调用者)------ 支持撤销
class RemoteControl {
#history = [];
execute(command) {
command.execute();
this.#history.push(command);
}
undo() {
const command = this.#history.pop();
if (command) command.undo();
else console.log('⚠ 没有可撤销的操作');
}
}
// 使用
const remote = new RemoteControl();
const light = new Light();
const tv = new TV();
// 一键"回家模式"
const homeMode = new MacroCommand();
homeMode.add(new LightOnCommand(light)).add(new TVOnCommand(tv));
remote.execute(homeMode);
// 💡 灯开了 → 📺 电视开了
remote.undo();
// 📺 电视关了 → 💡 灯关了(逆序撤销)
19. 职责链模式(Chain of Responsibility)
🎯 一句话理解
把请求沿着 链条 传递,每个节点决定自己处理还是传给下一个。像审批流程一样。
🏠 生活类比
公司报销审批:金额 < 500 → 组长批,500 ~ 5000 → 经理批,> 5000 → 总监批。报销单从组长开始往上递,每个人决定自己能不能批,批不了就传给上级。
💻 代码实现
javascript
// 处理器基类
class Handler {
#next = null;
setNext(handler) { this.#next = handler; return handler; }
handle(request) {
if (this.#next) return this.#next.handle(request);
return null;
}
}
// 具体处理器
class GroupLeader extends Handler {
handle(request) {
if (request.amount <= 500) {
return `✅ 组长批准: ¥${request.amount} (${request.reason})`;
}
console.log(`组长: ¥${request.amount} 超出权限,转交经理`);
return super.handle(request);
}
}
class Manager extends Handler {
handle(request) {
if (request.amount <= 5000) {
return `✅ 经理批准: ¥${request.amount} (${request.reason})`;
}
console.log(`经理: ¥${request.amount} 超出权限,转交总监`);
return super.handle(request);
}
}
class Director extends Handler {
handle(request) {
if (request.amount <= 50000) {
return `✅ 总监批准: ¥${request.amount} (${request.reason})`;
}
return `❌ 总监拒绝: ¥${request.amount} 金额过大,需要CEO审批`;
}
}
// 构建责任链
const leader = new GroupLeader();
const manager = new Manager();
const director = new Director();
leader.setNext(manager).setNext(director);
// 使用
console.log(leader.handle({ amount: 200, reason: '办公用品' }));
// ✅ 组长批准: ¥200 (办公用品)
console.log(leader.handle({ amount: 3000, reason: '团建费用' }));
// 组长: ¥3000 超出权限,转交经理
// ✅ 经理批准: ¥3000 (团建费用)
console.log(leader.handle({ amount: 20000, reason: '服务器采购' }));
// 组长 → 经理 → ✅ 总监批准: ¥20000 (服务器采购)
中间件链(Koa 风格):
javascript
class MiddlewareChain {
#middlewares = [];
use(middleware) {
this.#middlewares.push(middleware);
return this;
}
async execute(ctx) {
let index = 0;
const dispatch = async (i) => {
if (i >= this.#middlewares.length) return;
const middleware = this.#middlewares[i];
await middleware(ctx, () => dispatch(i + 1));
};
await dispatch(index);
return ctx;
}
}
// 使用 ------洋葱模型
const chain = new MiddlewareChain();
chain
.use(async (ctx, next) => {
console.log('🔑 认证中间件 - 开始');
ctx.user = { name: '张三' };
await next();
console.log('🔑 认证中间件 - 结束');
})
.use(async (ctx, next) => {
console.log(`📝 日志: ${ctx.user.name} 访问了 ${ctx.path}`);
await next();
})
.use(async (ctx, next) => {
console.log('⚡ 业务处理');
ctx.body = { message: '成功' };
await next();
});
chain.execute({ path: '/api/data' });
// 🔑 认证中间件 - 开始
// 📝 日志: 张三 访问了 /api/data
// ⚡ 业务处理
// 🔑 认证中间件 - 结束
20. 状态模式(State)
🎯 一句话理解
对象的 行为 取决于它的 状态。把每个状态的行为封装成独立的类,消除大量的状态判断 if-else。
🏠 生活类比
红绿灯:红灯 → 停车,绿灯 → 行驶,黄灯 → 减速。同样的"看到信号灯"动作,在不同状态下行为完全不同。
💻 代码实现
javascript
// 状态接口
class OrderState {
pay(order) { throw new Error('当前状态不支持支付'); }
ship(order) { throw new Error('当前状态不支持发货'); }
deliver(order) { throw new Error('当前状态不支持确认收货'); }
cancel(order) { throw new Error('当前状态不支持取消'); }
getName() { throw new Error('必须实现'); }
}
// 待支付状态
class PendingState extends OrderState {
pay(order) {
console.log('💰 支付成功!');
order.setState(new PaidState());
}
cancel(order) {
console.log('❌ 订单已取消');
order.setState(new CancelledState());
}
getName() { return '待支付'; }
}
// 已支付状态
class PaidState extends OrderState {
ship(order) {
console.log('📦 已发货!');
order.setState(new ShippedState());
}
cancel(order) {
console.log('💰 申请退款中...');
order.setState(new CancelledState());
}
getName() { return '已支付'; }
}
// 已发货状态
class ShippedState extends OrderState {
deliver(order) {
console.log('✅ 已确认收货!');
order.setState(new DeliveredState());
}
getName() { return '已发货'; }
}
// 已完成状态
class DeliveredState extends OrderState {
getName() { return '已完成'; }
}
// 已取消状态
class CancelledState extends OrderState {
getName() { return '已取消'; }
}
// 订单类
class Order {
#state;
constructor(orderId) {
this.orderId = orderId;
this.#state = new PendingState();
console.log(`📋 订单 ${orderId} 已创建,状态: ${this.getStatus()}`);
}
setState(state) {
console.log(`📊 状态变更: ${this.#state.getName()} → ${state.getName()}`);
this.#state = state;
}
getStatus() { return this.#state.getName(); }
pay() { this.#state.pay(this); }
ship() { this.#state.ship(this); }
deliver() { this.#state.deliver(this); }
cancel() { this.#state.cancel(this); }
}
// 使用 ------ 状态自动管理,不会出现非法操作
const order = new Order('ORD-001');
// 📋 订单 ORD-001 已创建,状态: 待支付
order.pay();
// 💰 支付成功!
// 📊 状态变更: 待支付 → 已支付
order.ship();
// 📦 已发货!
// 📊 状态变更: 已支付 → 已发货
order.deliver();
// ✅ 已确认收货!
// 📊 状态变更: 已发货 → 已完成
// order.ship(); // ❌ Error: 当前状态不支持发货(已完成不能再发货)
四、设计模式之间的关系
🕸️ 模式对比与关联
创建型 结构型 行为型
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 单例 │ │ 适配器 │ │ 观察者 │
│ 工厂 │ │ 装饰器 ←─┼─相似───→│ 发布订阅 │
│ 抽象工厂 │ │ 代理 ←──┼─相似 │ 策略 │
│ 建造者 │ │ 外观 │ │ 迭代器 │
│ 原型 │ │ 桥接 │ │ 模板方法 │
└──────────┘ │ 组合 │ │ 命令 │
│ 享元 │ │ 职责链 │
└──────────┘ │ 状态 │
└──────────┘
🔗 容易混淆的模式对比
| 对比组 | 核心区别 | 判断标准 |
|---|---|---|
| 工厂 vs 建造者 | 工厂关注"创建什么",建造者关注"怎么一步步创建" | 需要分步骤构建?→ 建造者 |
| 工厂 vs 抽象工厂 | 工厂创建一种产品,抽象工厂创建一系列产品 | 需要产品族?→ 抽象工厂 |
| 代理 vs 装饰器 | 代理控制访问,装饰器增强功能 | 是否限制访问?→ 代理 |
| 代理 vs 适配器 | 代理接口不变,适配器改变接口 | 接口不同?→ 适配器 |
| 外观 vs 代理 | 外观简化接口,代理控制访问 | 简化复杂性?→ 外观 |
| 观察者 vs 发布订阅 | 观察者直接通信,发布订阅通过中间人 | 有消息中心?→ 发布订阅 |
| 策略 vs 状态 | 策略由客户端选择,状态由对象内部切换 | 状态自动切换?→ 状态模式 |
| 模板方法 vs 策略 | 模板方法靠继承,策略靠组合 | 用继承还是组合? |
| 命令 vs 策略 | 命令关注"做什么"(可撤销),策略关注"怎么做" | 需要撤销?→ 命令 |
五、总结速查表
| # | 模式 | 类型 | 一句话 | 常见场景 |
|---|---|---|---|---|
| 1 | 单例 | 创建型 | 全局唯一实例 | Vuex Store、全局弹窗 |
| 2 | 工厂 | 创建型 | 你要什么我帮你造 | React.createElement、组件工厂 |
| 3 | 抽象工厂 | 创建型 | 一系列产品的工厂 | 跨平台UI组件库 |
| 4 | 建造者 | 创建型 | 一步步搭积木 | SQL Builder、axios配置 |
| 5 | 原型 | 创建型 | 克隆一个已有的 | Object.create、深拷贝 |
| 6 | 适配器 | 结构型 | 万能转换插头 | 第三方SDK对接、数据格式转换 |
| 7 | 装饰器 | 结构型 | 不改原物,层层增强 | HOC、函数AOP、TS装饰器 |
| 8 | 代理 | 结构型 | 找个中间人控制访问 | Vue3 reactive、缓存代理 |
| 9 | 外观 | 结构型 | 复杂系统一键操作 | API模块封装、智能家居 |
| 10 | 桥接 | 结构型 | 抽象与实现分离 | 消息系统(渠道×类型) |
| 11 | 组合 | 结构型 | 树形结构统一操作 | DOM树、组件树、文件系统 |
| 12 | 享元 | 结构型 | 共享减少内存 | 虚拟列表、字体对象池 |
| 13 | 观察者 | 行为型 | 变了就通知你 | Vue响应式、EventEmitter |
| 14 | 发布订阅 | 行为型 | 通过中间人间接通信 | EventBus、Redux、消息队列 |
| 15 | 策略 | 行为型 | 消灭if-else | 价格计算、表单验证 |
| 16 | 迭代器 | 行为型 | 一个一个往外拿 | for...of、自定义遍历 |
| 17 | 模板方法 | 行为型 | 骨架固定,细节自定义 | 数据解析流程、算法框架 |
| 18 | 命令 | 行为型 | 封装请求,支持撤销 | 宏命令、编辑器撤销重做 |
| 19 | 职责链 | 行为型 | 沿着链条传递 | 审批流程、中间件 |
| 20 | 状态 | 行为型 | 状态决定行为 | 订单状态机、红绿灯 |
六、学习路线图
入门(先掌握这5个) 进阶(再学这7个) 高级(最后8个)
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ ✅ 单例模式 │ │ 🔶 抽象工厂 │ │ 🔷 桥接模式 │
│ ✅ 工厂模式 │ │ 🔶 代理模式 │ │ 🔷 组合模式 │
│ ✅ 观察者模式 │ │ 🔶 外观模式 │ │ 🔷 享元模式 │
│ ✅ 策略模式 │ │ 🔶 装饰器模式 │ │ 🔷 迭代器模式 │
│ ✅ 发布-订阅 │ │ 🔶 适配器模式 │ │ 🔷 模板方法 │
│ │ │ 🔶 命令模式 │ │ 🔷 职责链模式 │
│ │ │ 🔶 状态模式 │ │ 🔷 建造者模式 │
│ │ │ │ │ 🔷 原型模式 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
📝 学习建议
- 不要死记硬背:理解每种模式解决什么问题,比记住代码更重要
- 多看源码:Vue、React、Koa、Express 中到处都是设计模式
- 刻意练习:在项目中遇到 if-else 地狱时,想想能不能用策略/状态模式
- 对比学习:把相似的模式放在一起比较(如代理 vs 装饰器 vs 适配器)
- 循序渐进:先掌握入门的5个,用到熟练了再学进阶的
🔍 源码中的设计模式
| 框架/库 | 用到的模式 |
|---|---|
| Vue 3 | 代理模式(reactive)、观察者模式(依赖收集)、发布订阅(事件总线)、策略模式(diff算法) |
| React | 工厂模式(createElement)、装饰器模式(HOC)、组合模式(组件树)、观察者模式(Hooks) |
| Koa | 职责链模式(中间件)、外观模式(ctx封装) |
| Express | 职责链模式(中间件)、工厂模式(Router)、模板方法(生命周期) |
| Axios | 适配器模式(xhr/fetch适配)、建造者模式(拦截器配置)、外观模式(API封装) |
| Redux | 发布订阅模式(subscribe)、单例模式(store)、命令模式(action) |
| RxJS | 观察者模式(核心)、迭代器模式(Observable)、职责链(Operator) |
💬 写在最后:设计模式不是银弹,不要为了用而用。当你发现代码越来越难维护、if-else 越来越多、改一个地方牵一发动全身的时候,再回头看看这篇文章,也许某个模式正好能帮到你。
好的代码不是一开始就设计出来的,而是在不断重构中打磨出来的。
如果觉得有帮助,请点个 👍 收藏,以后用得上的时候能找到。有问题欢迎评论区讨论!