Skip to content

JavaScript 设计模式

概述

设计模式是解决常见问题的可复用方案。

一、单例模式

javascript
class Singleton {
  static instance = null;
  
  static getInstance() {
    if (!Singleton.instance) {
      Singleton.instance = new Singleton();
    }
    return Singleton.instance;
  }
}

// 闭包实现
const createStore = (() => {
  let instance;
  return () => {
    if (!instance) {
      instance = { state: {} };
    }
    return instance;
  };
})();

二、观察者模式

javascript
class EventEmitter {
  constructor() {
    this.events = {};
  }
  
  on(event, callback) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(callback);
    return () => this.off(event, callback);
  }
  
  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));
  }
  
  once(event, callback) {
    const wrapper = (...args) => {
      callback(...args);
      this.off(event, wrapper);
    };
    this.on(event, wrapper);
  }
}

三、发布-订阅模式

javascript
// 与观察者区别:有中间调度中心
class PubSub {
  static channels = {};
  
  static subscribe(channel, callback) {
    if (!this.channels[channel]) {
      this.channels[channel] = [];
    }
    this.channels[channel].push(callback);
  }
  
  static publish(channel, data) {
    if (!this.channels[channel]) return;
    this.channels[channel].forEach(cb => cb(data));
  }
}

// 发布者和订阅者不直接通信
PubSub.subscribe('news', (data) => console.log(data));
PubSub.publish('news', { title: 'Hello' });

四、策略模式

javascript
const strategies = {
  A: (salary) => salary * 4,
  B: (salary) => salary * 3,
  C: (salary) => salary * 2
};

function calculateBonus(level, salary) {
  return strategies[level](salary);
}

calculateBonus('A', 10000); // 40000

五、代理模式

javascript
// 缓存代理
const mult = (...args) => args.reduce((a, b) => a * b);

const proxyMult = (() => {
  const cache = {};
  return (...args) => {
    const key = args.join(',');
    if (cache[key]) return cache[key];
    return (cache[key] = mult(...args));
  };
})();

// ES6 Proxy
const handler = {
  get(target, key) {
    console.log(`访问 ${key}`);
    return target[key];
  }
};
const proxy = new Proxy({ name: 'test' }, handler);

六、装饰器模式

javascript
// 函数装饰器
function log(fn) {
  return function (...args) {
    console.log(`Call ${fn.name} with`, args);
    return fn.apply(this, args);
  };
}

const add = log((a, b) => a + b);

// ES7 装饰器 (需要 Babel)
function readonly(target, key, descriptor) {
  descriptor.writable = false;
  return descriptor;
}

class Example {
  @readonly
  name = 'test';
}

七、工厂模式

javascript
class UserFactory {
  static create(type) {
    switch (type) {
      case 'admin':
        return new Admin();
      case 'user':
        return new User();
      default:
        throw new Error('Unknown type');
    }
  }
}

面试高频题

Q1: 观察者 vs 发布-订阅?

观察者:主体直接通知观察者。发布-订阅:通过中间调度中心,解耦更彻底。

Q2: 前端常用设计模式?

单例 (Store)、观察者 (事件系统)、策略 (表单验证)、代理 (缓存)。

Q3: Vue 用了什么模式?

观察者 (响应式)、发布-订阅 (事件总线)、代理 (Proxy)。

前端面试知识库