面试中,设计模式是考察候选人对代码设计能力和编程思想的重要维度。本文整理了前端面试中最常见的设计模式问题,包含详细的解答和代码实现,帮助你在面试中脱颖而出。
一、基础概念面试题
1. 请解释什么是设计模式?
参考回答:
设计模式是软件开发中经过验证的解决方案,是对代码结构的最佳实践总结。它不是具体的代码实现,而是一种编程思想,旨在解决常见的代码设计问题。
设计模式的核心价值:
- 可复用性:相同问题不用重复造轮子
- 可维护性:代码结构清晰,易于理解和修改
- 可扩展性:方便添加新功能
- 沟通效率:开发团队有共同的设计语言
2. 设计模式分为哪几类?
参考回答:
设计模式主要分为三大类:
-
创建型模式(5种):关注对象的创建
- 单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式
-
结构型模式(7种):关注对象和类的组合
- 代理模式、装饰器模式、适配器模式、组合模式、桥接模式、外观模式、享元模式
-
行为型模式(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 核心设计模式:
-
观察者模式
- 数据响应式系统(dep、watcher)
- Vue2:
Object.defineProperty - Vue3:
Proxy
-
发布订阅模式
- EventBus (
$on、$emit) - 组件通信
- EventBus (
-
单例模式
- Vuex/Pinia 全局状态管理
- 全局组件
-
装饰器模式
mixinsVue.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 核心设计模式:
-
组合模式
- Children API
- Props 传递
-
高阶组件 HOC(装饰器模式)
connect()(react-redux)withRouter()(react-router-dom)
-
发布订阅模式
EventEmitter- Redux 中间件
-
策略模式
- 不同 diff 算法策略
- 优先级调度
javascript
// React HOC 示例
function withLoading(Component) {
return function WithLoadingComponent({ isLoading, ...props }) {
if (isLoading) {
return <Spinner />;
}
return <Component {...props} />;
};
}
7. Redux 用了哪些设计模式?
Redux 是前端状态管理的经典案例,它巧妙运用了多种设计模式:
-
单例模式
- 整个应用只有一个 store
-
发布订阅模式
subscribe()订阅状态变化
-
中间件模式
- 洋葱模型(Redux Middleware)
-
装饰器模式
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));
}
}
}
总结
面试中设计模式的考察主要分为三个层次:
- 概念层:理解各种设计模式的定义和特点
- 实现层:能够手写核心代码
- 应用层:理解框架源码中的设计模式应用
建议大家在准备面试时,不仅要记住概念,更要动手实践,理解每个模式背后的设计思想。
最后祝大家面试顺利,拿下心仪的offer!