1. 工厂模式 (Factory Pattern)
核心思想: 定义一个用于创建对象的接口(函数或方法),让子类或调用方决定实例化哪一个类。工厂模式使一个类的实例化延迟到其子类或调用时进行。
通俗比喻: 想象一个“快餐店”的点餐台(这就是工厂)。你只需要告诉收银员你想要“汉堡”还是“薯条”(传入参数),而不需要关心后厨(具体实现)是如何制作这些食物的。点餐台负责根据你的要求,给你正确的成品。
前端应用场景:
- UI 组件库: 根据传入的类型字符串(如
'button'
,'input'
)创建不同的 VNode 或 React 元素。 - 消除
if/else
或switch
: 当需要根据不同条件创建不同对象实例时,使用工厂模式可以替代冗长的条件判断,让代码更符合“开放-封闭原则”。
- UI 组件库: 根据传入的类型字符串(如
代码示例:
javascript// UI组件工厂 class VButton { render() { console.log('渲染一个按钮'); } } class VInput { render() { console.log('渲染一个输入框'); } } function ComponentFactory(type) { switch (type) { case 'button': return new VButton(); case 'input': return new VInput(); default: throw new Error('未知的组件类型'); } } const myButton = ComponentFactory('button'); // -> new VButton() const myInput = ComponentFactory('input'); // -> new VInput() myButton.render(); // 输出: 渲染一个按钮 myInput.render(); // 输出: 渲染一个输入框
2. 代理模式 (Proxy Pattern)
核心思想: 为目标对象创建一个代理对象,外界通过操作这个代理对象来间接地访问目标对象。代理可以在这个过程中进行额外的控制、增强或修改。
通俗比喻: 就像一个“明星经纪人”(代理)。你不能直接联系到明星本人(目标对象),所有采访、工作邀约都必须通过经纪人。经纪人可以帮你过滤掉不合适的工作(访问控制)、安排日程(功能增强),但最终执行工作的还是明星本人。
前端应用场景:
- 数据响应式: 这是 Vue 3 的响应式核心。通过
new Proxy(data, handler)
劫持对数据的读写操作,当数据被修改时,代理的set
捕获器会得到通知,从而触发视图更新。 - 事件委托/事件代理: 在父元素上监听事件,通过代理来处理所有子元素的事件,也是一种代理思想的应用。
- 数据校验与缓存: 在访问或设置对象属性前,进行数据格式校验;或者在请求数据时,代理可以先检查缓存中是否存在,若存在则直接返回缓存数据。
- 数据响应式: 这是 Vue 3 的响应式核心。通过
代码示例 (ES6 Proxy):
javascript// 目标对象 const targetData = { name: '张三', _password: '123', // 私有属性 }; // 代理处理器 const handler = { get(target, key) { if (key.startsWith('_')) { throw new Error('不能访问私有属性'); } console.log(`正在读取属性: ${key}`); return Reflect.get(target, key); }, set(target, key, value) { console.log(`正在设置属性: ${key} -> ${value}`); return Reflect.set(target, key, value); }, }; const proxyData = new Proxy(targetData, handler); console.log(proxyData.name); // 输出: 正在读取属性: name \n 张三 proxyData.name = '李四'; // 输出: 正在设置属性: name -> 李四 // console.log(proxyData._password); // 抛出错误: 不能访问私有属性
3. 单例模式 (Singleton Pattern)
核心思想: 确保一个类在整个应用程序的生命周期中,只有一个实例存在,并提供一个全局唯一的访问点来获取该实例。
通俗比喻: 就像“月亮”只有一个。无论你在地球的哪个角落、在什么时间去看,看到的都是同一个月亮。我们都通过“月亮”这个统一的名字来指代它。
前端应用场景:
- 全局状态管理: Vuex/Redux/Pinia 的
store
,在整个应用中是唯一的,所有组件共享和操作这一个状态实例。 - 全局唯一的 UI 组件: 例如一个全局的
Message
提示框或Modal
登录框,我们希望无论调用多少次,页面上只维护一个实例。 - 全局配置或缓存: 创建一个全局唯一的对象来存储应用的配置信息或缓存数据。
- 全局状态管理: Vuex/Redux/Pinia 的
代码示例 (使用类实现):
javascriptclass GlobalStore { static instance; constructor() { if (GlobalStore.instance) { return GlobalStore.instance; } this.data = {}; // 初始化数据 GlobalStore.instance = this; } setItem(key, value) { this.data[key] = value; } getItem(key) { return this.data[key]; } } const store1 = new GlobalStore(); const store2 = new GlobalStore(); console.log(store1 === store2); // 输出: true,它们是同一个实例 store1.setItem('user', { name: '张三' }); console.log(store2.getItem('user')); // 输出: { name: '张三' }
4. 装饰器模式 (Decorator Pattern)
核心思想: 在不改变原对象结构和继承关系的前提下,动态地为对象添加新的功能。它是通过将对象包装在另一个具有新功能的对象中来实现的。
通俗比喻: 就像给一杯“原味咖啡”(原对象)加“牛奶”、“糖”或“奶油”(装饰器)。每加一种东西,咖啡的功能(味道)就增加一层,但咖啡本身并没有改变。你可以自由组合这些“装饰”,得到一杯“拿铁”或“卡布奇诺”。
前端应用场景:
- React 中的高阶组件 (HOC):
withRouter
、connect
(Redux) 等都是典型的装饰器模式。它们接收一个组件作为参数,返回一个增强了功能(如路由信息、全局状态)的新组件。 - TypeScript/Babel 中的装饰器语法 (
@
): 这是该模式的语法糖实现,常用于给类或方法添加日志、权限校验、性能分析等非核心业务的功能。
- React 中的高阶组件 (HOC):
代码示例 (使用 TypeScript/Babel 的@语法):
typescript// 装饰器函数:在方法执行前打印日志 function log(target: any, key: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function (...args: any[]) { console.log(`LOG: Entering method '${key}'.`); const result = originalMethod.apply(this, args); console.log(`LOG: Exiting method '${key}'.`); return result; }; return descriptor; } class Calculator { @log add(a: number, b: number): number { return a + b; } } const calc = new Calculator(); calc.add(2, 3); // 输出: // LOG: Entering method 'add'. // LOG: Exiting method 'add'.
5. 策略模式 (Strategy Pattern)
核心思想: 定义一组可相互替换的算法(策略),并将每一个算法封装起来,使它们可以独立于使用它们的客户而变化。
通俗比喻: 就像使用“地图 App 导航”(上下文)。当你设定了目的地后,App 会提供多种出行方案(策略),如“最快路线”、“躲避拥堵”、“步行”、“公交”。你(客户)可以选择任何一种方案,导航 App 会根据你选择的策略来执行,而不需要改变导航这个行为本身。
前端应用场景:
- 表单验证: 针对一个字段,可以应用“非空”、“最小长度”、“邮箱格式”等不同的验证策略。
- 薪资计算: 根据员工的级别(如'S', 'A', 'B'),应用不同的绩效奖金计算策略。
- 动画缓动: 实现动画效果时,可以传入不同的缓动函数(策略),如 'linear', 'ease-in', 'ease-out'。
代码示例 (薪资计算):
javascript// 策略对象 const strategies = { S: (salary) => salary * 4, A: (salary) => salary * 3, B: (salary) => salary * 2, }; // 上下文(Context) function calculateBonus(level, salary) { if (strategies[level]) { return strategies[level](salary); } return 0; // 默认没有奖金 } console.log(calculateBonus('S', 20000)); // 输出: 80000 console.log(calculateBonus('A', 15000)); // 输出: 45000
6. 发布-订阅模式 (Publish/Subscribe Pattern) & 观察者模式 (Observer Pattern)
这两个模式关系紧密,目的都是为了解决对象间的解耦和消息通知,但实现上有一个关键区别。
核心思想:
- 观察者模式: 存在一个“主题 (Subject)”和多个“观察者 (Observer)”。观察者直接订阅主题,当主题状态变化时,它会直接通知所有已订阅的观察者。发布者和订阅者是直接知晓对方的。
- 发布-订阅模式: 存在“发布者 (Publisher)”、“订阅者 (Subscriber)”和一个“事件中心/调度中心 (Broker/Event Bus)”。发布者和订阅者之间完全解耦,它们都只与事件中心交互。发布者向中心发布消息,中心再把消息派发给所有订阅了该消息的订阅者。发布者和订阅者互不知晓。
通俗比喻:
- 观察者模式: 你去“报社”(主题)登记你的地址(订阅),报社每天有了新报纸,会按照登记的地址直接送到你家(通知)。报社知道它的每一个订阅者。
- 发布-订阅模式: 你在“微信”(事件中心)上关注了某个“公众号”(发布者)。公众号发布新文章时,是告诉微信平台“我发了新内容”,然后微信平台去通知所有关注它的你(订阅者)。公众号并不知道你是谁,你也不知道公众号的服务器在哪,你们都只依赖微信平台。
前端应用场景:
- 观察者模式:
- Vue/React 的数据响应式: 数据(主题)变化,直接通知依赖它的组件(观察者)进行更新。
- 发布-订阅模式:
- 全局事件总线 (Event Bus): 在大型应用中,用于两个不相关的组件之间通信。例如,一个 Header 组件的用户退出操作,需要通知一个 Content 组件清空数据。
- DOM 事件:
element.addEventListener('click', handler)
,这里的浏览器事件模型更像是发布-订阅,element
是发布者,handler
是订阅者,而浏览器内部的事件循环和分发机制就是那个“事件中心”。
- 观察者模式:
代码示例 (发布-订阅模式):
javascript// 事件中心 (Broker) class EventBus { constructor() { this.listeners = {}; } // 订阅 subscribe(topic, callback) { if (!this.listeners[topic]) { this.listeners[topic] = []; } this.listeners[topic].push(callback); } // 发布 publish(topic, data) { if (this.listeners[topic]) { this.listeners[topic].forEach((callback) => callback(data)); } } } const bus = new EventBus(); // 订阅者1 (Component A) bus.subscribe('user:logout', () => { console.log('组件A收到通知:清空用户数据。'); }); // 订阅者2 (Component B) bus.subscribe('user:logout', () => { console.log('组件B收到通知:跳转到登录页。'); }); // 发布者 (Header Component) 在某个时刻发布消息 setTimeout(() => { console.log('用户点击了退出按钮,发布登出事件...'); bus.publish('user:logout'); }, 1000);