Skip to content

深入理解 JavaScript 设计模式

单例、观察者、发布订阅、策略、代理、装饰器、工厂模式,结合前端框架源码实战

什么是设计模式?

定义:设计模式(Design Pattern)是针对软件设计中反复出现的问题所总结出的通用解决方案。它不是具体的代码,而是一种经过验证的、可复用的设计思想和模板,帮助开发者写出可维护、可扩展、低耦合的代码。

涉及场景

  • 状态管理:Redux(发布订阅)、Vuex(观察者)的核心架构
  • 组件通信:EventBus(发布订阅)、Context(中介者)
  • 权限与拦截:axios 拦截器(装饰器/责任链)、路由守卫(策略)
  • 实例管理:全局弹窗、日志服务(单例模式)
  • UI 组件库:根据类型动态创建组件(工厂模式)
  • 数据响应式:Vue3 reactive(代理模式)、计算属性(观察者)

作用

  1. 提升代码质量:经过验证的方案避免重复踩坑
  2. 统一团队语言:团队成员用"观察者模式"、"策略模式"等术语沟通更高效
  3. 框架源码阅读:主流框架大量使用设计模式,理解模式才能读懂源码
  4. 面试加分项:能结合实际项目讲解设计模式的应用是中高级面试的加分点

创建型模式

单例模式(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 发布订阅的区别                                  │
│ • 单例模式的多种实现                                        │
│ • 前端框架中用了哪些设计模式                                 │
└──────────────────────────────────────────────────────────┘