Skip to content

1. 工厂模式 (Factory Pattern)

  • 核心思想: 定义一个用于创建对象的接口(函数或方法),让子类或调用方决定实例化哪一个类。工厂模式使一个类的实例化延迟到其子类或调用时进行。

  • 通俗比喻: 想象一个“快餐店”的点餐台(这就是工厂)。你只需要告诉收银员你想要“汉堡”还是“薯条”(传入参数),而不需要关心后厨(具体实现)是如何制作这些食物的。点餐台负责根据你的要求,给你正确的成品。

  • 前端应用场景:

    • UI 组件库: 根据传入的类型字符串(如 'button', 'input')创建不同的 VNode 或 React 元素。
    • 消除 if/elseswitch: 当需要根据不同条件创建不同对象实例时,使用工厂模式可以替代冗长的条件判断,让代码更符合“开放-封闭原则”。
  • 代码示例:

    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 捕获器会得到通知,从而触发视图更新。
    • 事件委托/事件代理: 在父元素上监听事件,通过代理来处理所有子元素的事件,也是一种代理思想的应用。
    • 数据校验与缓存: 在访问或设置对象属性前,进行数据格式校验;或者在请求数据时,代理可以先检查缓存中是否存在,若存在则直接返回缓存数据。
  • 代码示例 (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登录框,我们希望无论调用多少次,页面上只维护一个实例。
    • 全局配置或缓存: 创建一个全局唯一的对象来存储应用的配置信息或缓存数据。
  • 代码示例 (使用类实现):

    javascript
    class 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): withRouterconnect (Redux) 等都是典型的装饰器模式。它们接收一个组件作为参数,返回一个增强了功能(如路由信息、全局状态)的新组件。
    • TypeScript/Babel 中的装饰器语法 (@): 这是该模式的语法糖实现,常用于给类或方法添加日志、权限校验、性能分析等非核心业务的功能。
  • 代码示例 (使用 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);