手写三大核心:Promise、Event Bus、深拷贝
这三道题是面试中的"照妖镜",能写出来才算真正理解。我给你逐行讲解。
一、手写深拷贝(最常考,也最容易暴露问题)
基础版(只考虑对象和数组)
javascript
function deepClone(obj) {
// 处理 null 和基本类型
if (obj === null || typeof obj !== 'object') {
return obj
}
// 处理数组和对象
const clone = Array.isArray(obj) ? [] : {}
for (let key in obj) {
// 只拷贝自身属性,不拷贝原型链上的
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key])
}
}
return clone
}
进阶版(解决循环引用 + 处理特殊类型)
javascript
function deepClone(obj, hash = new WeakMap()) {
// 处理 null 和 undefined
if (obj === null || typeof obj !== 'object') return obj
// 处理循环引用:如果已经拷贝过,直接返回
if (hash.has(obj)) return hash.get(obj)
// 处理 Date
if (obj instanceof Date) return new Date(obj)
// 处理 RegExp
if (obj instanceof RegExp) return new RegExp(obj)
// 处理函数(通常直接复用,不需要深拷贝)
if (typeof obj === 'function') return obj
// 创建新对象(保留原型链)
const clone = Array.isArray(obj) ? [] : Object.create(Object.getPrototypeOf(obj))
hash.set(obj, clone)
// 拷贝 Symbol 类型的 key
const symbols = Object.getOwnPropertySymbols(obj)
for (let sym of symbols) {
clone[sym] = deepClone(obj[sym], hash)
}
// 拷贝普通属性
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key], hash)
}
}
return clone
}
// 测试
const obj = { a: 1, b: { c: 2 } }
obj.self = obj // 循环引用
const cloned = deepClone(obj)
console.log(cloned) // 不会栈溢出
简化版(面试最优解)
javascript
function deepClone(obj, cache = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj
if (cache.has(obj)) return cache.get(obj)
const clone = Array.isArray(obj) ? [] : {}
cache.set(obj, clone)
Reflect.ownKeys(obj).forEach(key => {
clone[key] = deepClone(obj[key], cache)
})
return clone
}
关键点:
- WeakMap 解决循环引用(不用 Map 是因为 WeakMap 的 key 是弱引用,不会阻止垃圾回收)
- Reflect.ownKeys 可以拿到 Symbol 和不可枚举属性
- 函数通常不需要深拷贝,直接用原函数
二、手写 Event Bus(发布订阅模式)
javascript
class EventBus {
constructor() {
// 存储事件和对应的回调函数
// 格式:{ eventName: [handler1, handler2, ...] }
this.events = {}
}
// 订阅事件
on(eventName, callback) {
if (!this.events[eventName]) {
this.events[eventName] = []
}
this.events[eventName].push(callback)
// 返回取消订阅的函数(方便 React useEffect 清理)
return () => this.off(eventName, callback)
}
// 订阅一次(触发后自动移除)
once(eventName, callback) {
const onceWrapper = (...args) => {
callback(...args)
this.off(eventName, onceWrapper)
}
// 保存原始回调,以便在 off 时能正确移除
onceWrapper.raw = callback
this.on(eventName, onceWrapper)
}
// 触发事件
emit(eventName, ...args) {
const callbacks = this.events[eventName]
if (!callbacks || callbacks.length === 0) return
// 遍历执行(注意:防止在执行过程中数组被修改)
callbacks.forEach(cb => {
try {
cb(...args)
} catch (error) {
console.error(`EventBus error in ${eventName}:`, error)
}
})
}
// 移除事件
off(eventName, callback) {
if (!callback) {
// 如果没有传 callback,则移除整个事件
delete this.events[eventName]
return
}
const callbacks = this.events[eventName]
if (!callbacks) return
// 找到并移除
const index = callbacks.findIndex(cb => cb === callback || cb.raw === callback)
if (index !== -1) {
callbacks.splice(index, 1)
}
// 如果该事件没有回调了,删除这个 key
if (callbacks.length === 0) {
delete this.events[eventName]
}
}
// 清空所有事件
clear() {
this.events = {}
}
}
// 使用示例
const bus = new EventBus()
const handler = (data) => console.log('收到:', data)
bus.on('click', handler)
bus.emit('click', { x: 100, y: 200 }) // 收到: { x: 100, y: 200 }
bus.once('once', () => console.log('只执行一次'))
bus.emit('once') // 输出
bus.emit('once') // 不输出
bus.off('click', handler) // 移除
Vue 风格的事件总线:
javascript
class VueEventBus {
constructor() {
this.events = new Map()
}
$on(event, handler) {
if (!this.events.has(event)) {
this.events.set(event, new Set())
}
this.events.get(event).add(handler)
}
$emit(event, ...args) {
const handlers = this.events.get(event)
if (handlers) {
handlers.forEach(handler => handler(...args))
}
}
$off(event, handler) {
if (!handler) {
this.events.delete(event)
return
}
const handlers = this.events.get(event)
if (handlers) {
handlers.delete(handler)
if (handlers.size === 0) this.events.delete(event)
}
}
$once(event, handler) {
const wrapper = (...args) => {
handler(...args)
this.$off(event, wrapper)
}
this.$on(event, wrapper)
}
}
三、手写 Promise(核心实现)
javascript
class MyPromise {
static PENDING = 'pending'
static FULFILLED = 'fulfilled'
static REJECTED = 'rejected'
constructor(executor) {
this.status = MyPromise.PENDING
this.value = undefined
this.reason = undefined
// 存储成功回调(支持多次 then 调用)
this.onFulfilledCallbacks = []
// 存储失败回调
this.onRejectedCallbacks = []
const resolve = (value) => {
if (this.status === MyPromise.PENDING) {
this.status = MyPromise.FULFILLED
this.value = value
// 执行所有成功回调
this.onFulfilledCallbacks.forEach(fn => fn())
}
}
const reject = (reason) => {
if (this.status === MyPromise.PENDING) {
this.status = MyPromise.REJECTED
this.reason = reason
this.onRejectedCallbacks.forEach(fn => fn())
}
}
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
then(onFulfilled, onRejected) {
// 参数可选,透传值
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
// 返回新的 Promise,实现链式调用
return new MyPromise((resolve, reject) => {
const handleCallback = (callback, value, resolve, reject) => {
// 微任务执行
queueMicrotask(() => {
try {
const result = callback(value)
// 如果返回的是 Promise,则等待其完成
if (result instanceof MyPromise) {
result.then(resolve, reject)
} else {
resolve(result)
}
} catch (error) {
reject(error)
}
})
}
if (this.status === MyPromise.FULFILLED) {
handleCallback(onFulfilled, this.value, resolve, reject)
} else if (this.status === MyPromise.REJECTED) {
handleCallback(onRejected, this.reason, resolve, reject)
} else {
// pending 状态:存储回调
this.onFulfilledCallbacks.push(() => {
handleCallback(onFulfilled, this.value, resolve, reject)
})
this.onRejectedCallbacks.push(() => {
handleCallback(onRejected, this.reason, resolve, reject)
})
}
})
}
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 })
)
}
// 静态方法
static resolve(value) {
if (value instanceof MyPromise) return value
return new MyPromise(resolve => resolve(value))
}
static reject(reason) {
return new MyPromise((_, reject) => reject(reason))
}
static all(promises) {
return new MyPromise((resolve, reject) => {
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument must be an array'))
}
const results = []
let completed = 0
if (promises.length === 0) {
resolve(results)
return
}
promises.forEach((promise, index) => {
MyPromise.resolve(promise).then(
value => {
results[index] = value
completed++
if (completed === promises.length) {
resolve(results)
}
},
reject
)
})
})
}
static race(promises) {
return new MyPromise((resolve, reject) => {
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument must be an array'))
}
promises.forEach(promise => {
MyPromise.resolve(promise).then(resolve, reject)
})
})
}
static allSettled(promises) {
return new MyPromise((resolve) => {
const results = []
let completed = 0
if (promises.length === 0) {
resolve(results)
return
}
promises.forEach((promise, index) => {
MyPromise.resolve(promise).then(
value => {
results[index] = { status: 'fulfilled', value }
completed++
if (completed === promises.length) resolve(results)
},
reason => {
results[index] = { status: 'rejected', reason }
completed++
if (completed === promises.length) resolve(results)
}
)
})
})
}
static any(promises) {
return new MyPromise((resolve, reject) => {
const errors = []
let rejectedCount = 0
if (promises.length === 0) {
reject(new AggregateError([], 'All promises were rejected'))
}
promises.forEach((promise, index) => {
MyPromise.resolve(promise).then(
resolve,
error => {
errors[index] = error
rejectedCount++
if (rejectedCount === promises.length) {
reject(new AggregateError(errors, 'All promises were rejected'))
}
}
)
})
})
}
}
// 测试
const p = new MyPromise((resolve, reject) => {
setTimeout(() => resolve('success'), 1000)
})
p.then(res => {
console.log(res) // 1秒后输出 'success'
return 'next'
}).then(res => {
console.log(res) // 输出 'next'
})
四、面试时的策略
| 题目 | 必须实现的核心 | 加分项 |
|---|---|---|
| 深拷贝 | 递归 + 基础类型判断 | 循环引用(WeakMap) + Symbol + 原型链 |
| Event Bus | on/emit/off 基本逻辑 | once + 取消订阅返回值 + 错误处理 |
| Promise | then 链式调用 + 状态管理 | all/race + finally + 微任务 |
手写的核心价值不是代码完美,而是展示你对原理的理解。 把关键逻辑写清楚,比堆砌边界处理更重要。