前端设计模式面试题大全

面试中,设计模式是考察候选人对代码设计能力和编程思想的重要维度。本文整理了前端面试中最常见的设计模式问题,包含详细的解答和代码实现,帮助你在面试中脱颖而出。

一、基础概念面试题

1. 请解释什么是设计模式?

参考回答:

设计模式是软件开发中经过验证的解决方案,是对代码结构的最佳实践总结。它不是具体的代码实现,而是一种编程思想,旨在解决常见的代码设计问题。

设计模式的核心价值:

  • 可复用性:相同问题不用重复造轮子
  • 可维护性:代码结构清晰,易于理解和修改
  • 可扩展性:方便添加新功能
  • 沟通效率:开发团队有共同的设计语言

2. 设计模式分为哪几类?

参考回答:

设计模式主要分为三大类:

  1. 创建型模式(5种):关注对象的创建

    • 单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式
  2. 结构型模式(7种):关注对象和类的组合

    • 代理模式、装饰器模式、适配器模式、组合模式、桥接模式、外观模式、享元模式
  3. 行为型模式(11种):关注对象之间的通信

    • 观察者模式、策略模式、命令模式、模板方法模式、迭代器模式、中介者模式、备忘录模式、职责链模式、状态模式、访问者模式、解释器模式

二、手写代码题

3. 手写一个 Singleton(单例模式)

题目要求: 请用 JavaScript 实现一个单例模式

实现方式一:类实现

javascript 复制代码
class Singleton {
  constructor() {
    if (Singleton.instance) {
      return Singleton.instance;
    }
    Singleton.instance = this;
    this.data = [];
  }
  
  add(item) {
    this.data.push(item);
  }
  
  getData() {
    return this.data;
  }
}

const instance1 = new Singleton();
const instance2 = new Singleton();
console.log(instance1 === instance2); // true

实现方式二:闭包实现

javascript 复制代码
const Singleton = (function() {
  let instance = null;
  
  return function() {
    if (instance) {
      return instance;
    }
    instance = this;
    this.data = [];
  };
})();

Singleton.prototype.add = function(item) {
  this.data.push(item);
};

实现方式三:ES6 私有属性(最推荐)

javascript 复制代码
class Singleton {
  static #instance = null;
  
  constructor() {
    if (Singleton.#instance) {
      return Singleton.#instance;
    }
    Singleton.#instance = this;
    this.data = [];
  }
  
  static getInstance() {
    if (!Singleton.#instance) {
      Singleton.#instance = new Singleton();
    }
    return Singleton.#instance;
  }
}

// 使用
const s1 = Singleton.getInstance();
const s2 = Singleton.getInstance();
console.log(s1 === s2); // true

4. 手写一个 EventBus(观察者模式)

题目要求: 实现一个事件总线,支持 on、emit、off 方法

javascript 复制代码
class EventBus {
  constructor() {
    this.events = Object.create(null);
  }

  // 订阅事件
  on(event, callback) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(callback);
    
    // 返回取消订阅的函数
    return () => this.off(event, callback);
  }

  // 发布事件
  emit(event, ...args) {
    if (this.events[event]) {
      this.events[event].forEach(callback => {
        callback(...args);
      });
    }
  }

  // 取消订阅
  off(event, callback) {
    if (this.events[event]) {
      this.events[event] = this.events[event].filter(
        cb => cb !== callback
      );
    }
  }

  // 只订阅一次
  once(event, callback) {
    const wrapper = (...args) => {
      callback(...args);
      this.off(event, wrapper);
    };
    this.on(event, wrapper);
  }
}

// 测试
const bus = new EventBus();

const handleUserLogin = (user) => {
  console.log(`${user.name} 登录了`);
};

const unsubscribe = bus.on('login', handleUserLogin);

bus.emit('login', { name: 'qiao' });  // 输出: qiao 登录了
bus.emit('login', { name: 'tom' });   // 输出: tom 登录了

unsubscribe(); // 取消订阅
bus.emit('login', { name: 'jerry' });  // 不再输出

5. 手写一个简易版 Promise(工厂模式 + 状态模式)

javascript 复制代码
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
  constructor(executor) {
    this.state = PENDING;
    this.value = undefined;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {
      if (this.state === PENDING) {
        this.state = FULFILLED;
        this.value = value;
        this.onFulfilledCallbacks.forEach(cb => cb(value));
      }
    };

    const reject = (reason) => {
      if (this.state === PENDING) {
        this.state = REJECTED;
        this.value = reason;
        this.onRejectedCallbacks.forEach(cb => cb(reason));
      }
    };

    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }

  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      const handle = (callback) => {
        try {
          const result = callback(this.value);
          resolve(result);
        } catch (error) {
          reject(error);
        }
      };

      if (this.state === FULFILLED) {
        setTimeout(() => handle(onFulfilled), 0);
      } else if (this.state === REJECTED) {
        setTimeout(() => handle(onRejected), 0);
      } else {
        this.onFulfilledCallbacks.push(() => handle(onFulfilled));
        this.onRejectedCallbacks.push(() => handle(onRejected));
      }
    });
  }

  catch(onRejected) {
    return this.then(null, onRejected);
  }

  finally(callback) {
    return this.then(
      value => MyPromise.resolve(callback()).then(() => value),
      reason => MyPromise.resolve(callback()).then(() => { throw reason; })
    );
  }
}

// 静态方法
MyPromise.resolve = (value) => {
  return new MyPromise(resolve => resolve(value));
};

MyPromise.reject = (reason) => {
  return new MyPromise((_, reject) => reject(reason));
};

MyPromise.all = (promises) => {
  return new MyPromise((resolve, reject) => {
    const results = [];
    let count = 0;

    promises.forEach((promise, index) => {
      MyPromise.resolve(promise).then(
        value => {
          results[index] = value;
          count++;
          if (count === promises.length) {
            resolve(results);
          }
        },
        reject
      );
    });
  });
};

三、框架应用面试题

6. Vue/React 中用了哪些设计模式?

Vue 核心设计模式:

  1. 观察者模式

    • 数据响应式系统(dep、watcher)
    • Vue2: Object.defineProperty
    • Vue3: Proxy
  2. 发布订阅模式

    • EventBus ($on$emit)
    • 组件通信
  3. 单例模式

    • Vuex/Pinia 全局状态管理
    • 全局组件
  4. 装饰器模式

    • mixins
    • Vue.extend
    • 组合式 API
javascript 复制代码
// Vue3 响应式原理
const reactive = (target) => {
  return new Proxy(target, {
    get(target, prop, receiver) {
      track(target, prop);
      return Reflect.get(target, prop, receiver);
    },
    set(target, prop, value, receiver) {
      trigger(target, prop, value);
      return Reflect.set(target, prop, value, receiver);
    }
  });
};

React 核心设计模式:

  1. 组合模式

    • Children API
    • Props 传递
  2. 高阶组件 HOC(装饰器模式)

    • connect() (react-redux)
    • withRouter() (react-router-dom)
  3. 发布订阅模式

    • EventEmitter
    • Redux 中间件
  4. 策略模式

    • 不同 diff 算法策略
    • 优先级调度
javascript 复制代码
// React HOC 示例
function withLoading(Component) {
  return function WithLoadingComponent({ isLoading, ...props }) {
    if (isLoading) {
      return <Spinner />;
    }
    return <Component {...props} />;
  };
}

7. Redux 用了哪些设计模式?

Redux 是前端状态管理的经典案例,它巧妙运用了多种设计模式:

  1. 单例模式

    • 整个应用只有一个 store
  2. 发布订阅模式

    • subscribe() 订阅状态变化
  3. 中间件模式

    • 洋葱模型(Redux Middleware)
  4. 装饰器模式

    • applyMiddleware
javascript 复制代码
// Redux 中间件实现
const logger = store => next => action => {
  console.log('dispatching', action);
  const result = next(action);
  console.log('next state', store.getState());
  return result;
};

// 多个中间件组合(装饰器模式)
const applyMiddleware = (...middlewares) => {
  return (createStore) => (reducer, preloadedState) => {
    const store = createStore(reducer, preloadedState);
    let dispatch = store.dispatch;

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    };

    const chain = middlewares.map(middleware => middleware(middlewareAPI));
    dispatch = compose(...chain)(store.dispatch);

    return {
      ...store,
      dispatch
    };
  };
};

四、业务场景面试题

8. 如何设计一个防抖/节流函数?

防抖(Debounce): 等待一定时间后执行,如果期间再次触发则重置计时器

javascript 复制代码
function debounce(fn, delay, immediate = false) {
  let timer = null;
  
  return function(...args) {
    const context = this;
    
    // 立即执行模式
    if (immediate && !timer) {
      fn.apply(context, args);
    }
    
    // 清除之前的计时器
    if (timer) clearTimeout(timer);
    
    timer = setTimeout(() => {
      if (!immediate) {
        fn.apply(context, args);
      }
      timer = null;
    }, delay);
  };
}

// 使用场景:搜索框输入
const handleSearch = debounce((value) => {
  console.log('搜索:', value);
}, 300);

input.addEventListener('input', (e) => {
  handleSearch(e.target.value);
});

节流(Throttle): 一定时间内只执行一次

javascript 复制代码
function throttle(fn, delay) {
  let lastTime = 0;
  let timer = null;
  
  return function(...args) {
    const context = this;
    const now = Date.now();
    const remaining = delay - (now - lastTime);
    
    if (remaining <= 0) {
      // 立即执行
      if (timer) {
        clearTimeout(timer);
        timer = null;
      }
      lastTime = now;
      fn.apply(context, args);
    } else if (!timer) {
      // 设置定时器
      timer = setTimeout(() => {
        lastTime = Date.now();
        timer = null;
        fn.apply(context, args);
      }, remaining);
    }
  };
}

// 使用场景:滚动监听
const handleScroll = throttle(() => {
  console.log('滚动位置:', window.scrollY);
}, 100);

window.addEventListener('scroll', handleScroll);

9. 如何设计一个全局 Loading 组件?

javascript 复制代码
class LoadingManager {
  constructor() {
    this.count = 0;
    this.show = null;
    this.hide = null;
  }

  init(show, hide) {
    this.show = show;
    this.hide = hide;
  }

  showLoading() {
    this.count++;
    if (this.count === 1 && this.show) {
      this.show();
    }
  }

  hideLoading() {
    if (this.count > 0) {
      this.count--;
      if (this.count === 0 && this.hide) {
        this.hide();
      }
    }
  }

  // 包装 promise
  wrap(promise) {
    this.showLoading();
    return promise.finally(() => {
      this.hideLoading();
    });
  }
}

// 单例
export const loading = new LoadingManager();

// 使用
loading.init(
  () => console.log('显示 Loading'),
  () => console.log('隐藏 Loading')
);

// 包装 API 请求
const request = (url) => {
  return loading.wrap(fetch(url));
};

10. 实现一个累加器工厂

javascript 复制代码
// 策略模式:不同的累加策略
const strategies = {
  sum: (acc, curr) => acc + curr,
  multiply: (acc, curr) => acc * curr,
  max: (acc, curr) => Math.max(acc, curr),
  min: (acc, curr) => Math.min(acc, curr),
};

// 工厂函数
function createAccumulator(initialValue = 0, strategy = 'sum') {
  let value = initialValue;
  const compute = strategies[strategy];
  
  return {
    add(item) {
      value = compute(value, item);
      return this;
    },
    addMany(...items) {
      items.forEach(item => {
        value = compute(value, item);
      });
      return this;
    },
    getValue() {
      return value;
    },
    reset() {
      value = initialValue;
      return this;
    }
  };
}

// 使用
const sumAcc = createAccumulator(0, 'sum');
sumAcc.add(1).add(2).add(3);
console.log(sumAcc.getValue()); // 6

const maxAcc = createAccumulator(0, 'max');
maxAcc.addMany(1, 5, 3, 9, 2);
console.log(maxAcc.getValue()); // 9

五、高频面试追问

11. Vue2 和 Vue3 响应式原理的区别?

特性 Vue2 Vue3
实现方式 Object.defineProperty Proxy
拦截方式 属性 getter/setter 对象级别的代理
数组监听 重写数组方法 Proxy 直接代理
嵌套对象 递归遍历 懒加载(按需代理)
性能 初始化时遍历所有属性 懒初始化
支持 不支持 Map/Set/WeakMap 支持新增数据类型
javascript 复制代码
// Vue2 响应式
Object.defineProperty(obj, 'name', {
  get() {
    track();
    return value;
  },
  set(newValue) {
    trigger();
    value = newValue;
  }
});

// Vue3 响应式
const proxy = new Proxy(obj, {
  get(target, key, receiver) {
    track(target, key);
    return Reflect.get(target, key, receiver);
  },
  set(target, key, value, receiver) {
    trigger(target, key, value);
    return Reflect.set(target, key, value, receiver);
  }
});

12. 观察者模式和发布订阅模式的区别?

区别 观察者模式 发布订阅模式
角色关系 观察者直接Subject 发布者、订阅者、调度中心
耦合度 耦合紧密 解耦
通信方式 直接通信 通过调度中心中转
典型应用 Vue 响应式 EventBus、Node.js EventEmitter
javascript 复制代码
// 观察者模式
class Subject {
  constructor() {
    this.observers = [];
  }
  attach(observer) {
    this.observers.push(observer);
  }
  notify() {
    this.observers.forEach(o => o.update());
  }
}

// 发布订阅模式
class PubSub {
  constructor() {
    this.topics = {};
  }
  subscribe(topic, callback) {
    if (!this.topics[topic]) {
      this.topics[topic] = [];
    }
    this.topics[topic].push(callback);
  }
  publish(topic, data) {
    if (this.topics[topic]) {
      this.topics[topic].forEach(cb => cb(data));
    }
  }
}

总结

面试中设计模式的考察主要分为三个层次:

  1. 概念层:理解各种设计模式的定义和特点
  2. 实现层:能够手写核心代码
  3. 应用层:理解框架源码中的设计模式应用

建议大家在准备面试时,不仅要记住概念,更要动手实践,理解每个模式背后的设计思想。

最后祝大家面试顺利,拿下心仪的offer!

相关推荐
Cg136269159741 小时前
JS函数表示
前端·html
在屏幕前出油2 小时前
02. FastAPI——路由
服务器·前端·后端·python·pycharm·fastapi
勿芮介2 小时前
【大模型应用】在window/linux上卸载OpenClaw
java·服务器·前端
摸鱼仙人~2 小时前
前端面试手写核心 Cheat Sheet(终极精简版)
前端
Ashley_Amanda2 小时前
深入浅出Web Dynpro:SAP企业级Web应用开发全面解析
前端
方安乐2 小时前
概念:前端工程化实践
前端
数据中穿行2 小时前
迭代器设计模式全方位深度解析
设计模式
数据中穿行2 小时前
观察者设计模式全方位深度解析
设计模式
kyriewen2 小时前
Flexbox 完全指南:从此告别浮动,拥抱一维战神
前端·css·html