技术面试复盘:高频算法题的前端实现思路(防抖、节流、深拷贝等)

技术面试复盘:高频算法题的前端实现思路(防抖、节流、深拷贝等)


复盘目标

  • 以可复用的前端实现为主线,覆盖概念、边界与代码
  • 题目聚焦高频场景:防抖、节流、深拷贝、深比较、并发控制、柯里化与组合、Promise 工具、事件总线

防抖(Debounce)

ts 复制代码
type DebounceOptions = { leading?: boolean; trailing?: boolean; maxWait?: number }
export function debounce<T extends (...args: any[]) => any>(fn: T, wait = 100, options: DebounceOptions = {}) {
  let timer: any = null
  let lastArgs: any[] | null = null
  let lastThis: any
  let lastCallTime = 0
  let lastInvokeTime = 0
  const leading = options.leading === true
  const trailing = options.trailing !== false
  const maxWait = typeof options.maxWait === 'number' ? options.maxWait : 0
  function invoke(time: number) {
    const args = lastArgs
    const context = lastThis
    lastArgs = null
    lastThis = null
    lastInvokeTime = time
    return fn.apply(context, args as any)
  }
  function startTimer(pending: () => void, ms: number) {
    timer = setTimeout(pending, ms)
  }
  function remainingWait(time: number) {
    const sinceLastCall = time - lastCallTime
    const sinceLastInvoke = time - lastInvokeTime
    const waitTime = wait - sinceLastCall
    return maxWait ? Math.min(waitTime, maxWait - sinceLastInvoke) : waitTime
  }
  function shouldInvoke(time: number) {
    const sinceLastCall = time - lastCallTime
    const sinceLastInvoke = time - lastInvokeTime
    return lastCallTime === 0 || sinceLastCall >= wait || sinceLastCall < 0 || (maxWait && sinceLastInvoke >= maxWait)
  }
  function trailingInvoke(time: number) {
    if (trailing && lastArgs) return invoke(time)
    lastArgs = null
    lastThis = null
    return undefined
  }
  function debounced(this: any, ...args: any[]) {
    const time = Date.now()
    const isInvoking = shouldInvoke(time)
    lastArgs = args
    lastThis = this
    lastCallTime = time
    if (!timer) {
      if (leading) invoke(time)
      startTimer(timerExpired, remainingWait(time))
    } else if (maxWait) {
      startTimer(timerExpired, remainingWait(time))
    }
  }
  function timerExpired() {
    const time = Date.now()
    if (shouldInvoke(time)) {
      timer = null
      trailingInvoke(time)
    } else {
      startTimer(timerExpired, remainingWait(time))
    }
  }
  ;(debounced as any).cancel = () => {
    if (timer) clearTimeout(timer)
    timer = null
    lastArgs = null
    lastThis = null
    lastCallTime = 0
    lastInvokeTime = 0
  }
  ;(debounced as any).flush = () => {
    if (timer) {
      clearTimeout(timer)
      timer = null
      return trailingInvoke(Date.now())
    }
  }
  return debounced as T & { cancel: () => void; flush: () => any }
}

节流(Throttle)

ts 复制代码
type ThrottleOptions = { leading?: boolean; trailing?: boolean }
export function throttle<T extends (...args: any[]) => any>(fn: T, wait = 100, options: ThrottleOptions = {}) {
  let timer: any = null
  let lastArgs: any[] | null = null
  let lastThis: any
  let lastInvoke = 0
  const leading = options.leading !== false
  const trailing = options.trailing !== false
  function invoke(time: number) {
    lastInvoke = time
    const res = fn.apply(lastThis, lastArgs as any)
    lastArgs = null
    lastThis = null
    return res
  }
  function trailingInvoke() {
    if (trailing && lastArgs) invoke(Date.now())
  }
  function throttled(this: any, ...args: any[]) {
    const time = Date.now()
    if (!lastInvoke && !leading) lastInvoke = time
    const remaining = wait - (time - lastInvoke)
    lastArgs = args
    lastThis = this
    if (remaining <= 0 || remaining > wait) {
      if (timer) {
        clearTimeout(timer)
        timer = null
      }
      invoke(time)
    } else if (!timer && trailing) {
      timer = setTimeout(() => {
        timer = null
        trailingInvoke()
      }, remaining)
    }
  }
  ;(throttled as any).cancel = () => {
    if (timer) clearTimeout(timer)
    timer = null
    lastArgs = null
    lastThis = null
    lastInvoke = 0
  }
  return throttled as T & { cancel: () => void }
}

深拷贝(Deep Clone)

ts 复制代码
export function deepClone<T>(input: T, cache = new WeakMap()): T {
  if (typeof input !== 'object' || input === null) return input
  if (cache.has(input as any)) return cache.get(input as any)
  if (input instanceof Date) return new Date(input.getTime()) as any
  if (input instanceof RegExp) return new RegExp(input.source, input.flags) as any
  if (input instanceof Map) {
    const m = new Map()
    cache.set(input as any, m as any)
    for (const [k, v] of input as any as Map<any, any>) m.set(deepClone(k, cache), deepClone(v, cache))
    return m as any
  }
  if (input instanceof Set) {
    const s = new Set()
    cache.set(input as any, s as any)
    for (const v of input as any as Set<any>) s.add(deepClone(v, cache))
    return s as any
  }
  if (ArrayBuffer.isView(input)) {
    const Ctor: any = (input as any).constructor
    return new Ctor((input as any))
  }
  const isArray = Array.isArray(input)
  const proto = Object.getPrototypeOf(input as any)
  const result: any = isArray ? [] : Object.create(proto)
  cache.set(input as any, result)
  for (const key of Reflect.ownKeys(input as any)) {
    const desc = Object.getOwnPropertyDescriptor(input as any, key)!
    if (desc.get || desc.set) Object.defineProperty(result, key, desc)
    else result[key as any] = deepClone((input as any)[key as any], cache)
  }
  return result
}

深比较(Deep Equal)

ts 复制代码
export function deepEqual(a: any, b: any, seen = new WeakMap()): boolean {
  if (Object.is(a, b)) return true
  if (typeof a !== typeof b) return false
  if (typeof a !== 'object' || a === null || b === null) return false
  if (seen.get(a) === b) return true
  seen.set(a, b)
  if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime()
  if (a instanceof RegExp && b instanceof RegExp) return a.source === b.source && a.flags === b.flags
  if (a instanceof Map && b instanceof Map) {
    if (a.size !== b.size) return false
    for (const [k, v] of a) if (!b.has(k) || !deepEqual(v, b.get(k), seen)) return false
    return true
  }
  if (a instanceof Set && b instanceof Set) {
    if (a.size !== b.size) return false
    for (const v of a) if (![...b].some(x => deepEqual(v, x, seen))) return false
    return true
  }
  const keysA = Reflect.ownKeys(a)
  const keysB = Reflect.ownKeys(b)
  if (keysA.length !== keysB.length) return false
  for (const k of keysA) {
    const da = Object.getOwnPropertyDescriptor(a, k)
    const db = Object.getOwnPropertyDescriptor(b, k)
    if (!!da?.get !== !!db?.get || !!da?.set !== !!db?.set) return false
    if (da && !da.get && !da.set) if (!deepEqual((a as any)[k as any], (b as any)[k as any], seen)) return false
  }
  return true
}

并发控制(限制同时执行的任务数)

ts 复制代码
export function createScheduler(limit = 5) {
  let active = 0
  const queue: Array<{ task: () => Promise<any>; resolve: (v: any) => void; reject: (e: any) => void }> = []
  function run() {
    while (active < limit && queue.length) {
      const item = queue.shift()!
      active++
      Promise.resolve().then(item.task).then(v => { active--; item.resolve(v); run() }).catch(e => { active--; item.reject(e); run() })
    }
  }
  return function add(task: () => Promise<any>) {
    return new Promise((resolve, reject) => { queue.push({ task, resolve, reject }); run() })
  }
}

柯里化与组合

ts 复制代码
export function curry(fn: Function, arity = fn.length) {
  function curried(this: any, ...args: any[]) {
    if (args.length >= arity) return fn.apply(this, args)
    return (...rest: any[]) => curried.apply(this, args.concat(rest))
  }
  return curried
}
export const compose = (...fns: Function[]) => (x: any) => fns.reduceRight((v, f) => f(v), x)

Promise 工具

ts 复制代码
export function timeout<T>(p: Promise<T>, ms = 2000) {
  return Promise.race([p, new Promise<T>((_, rej) => setTimeout(() => rej(new Error('timeout')), ms))])
}
export async function retry<T>(fn: () => Promise<T>, times = 3, delay = 200, factor = 2) {
  let d = delay
  for (let i = 0; i < times; i++) {
    try { return await fn() } catch { if (i === times - 1) throw new Error('retry failed'); await new Promise(r => setTimeout(r, d)); d *= factor }
  }
}
export function allSettled<T>(arr: Iterable<T | Promise<T>>) {
  return Promise.all(Array.from(arr, p => Promise.resolve(p).then(v => ({ status: 'fulfilled', value: v })).catch(e => ({ status: 'rejected', reason: e }))))
}

事件总线(EventEmitter)

ts 复制代码
export class Emitter {
  private store = new Map<string, Set<Function>>()
  on(event: string, handler: Function) { if (!this.store.has(event)) this.store.set(event, new Set()); this.store.get(event)!.add(handler); return () => this.off(event, handler) }
  once(event: string, handler: Function) { const wrap = (...args: any[]) => { this.off(event, wrap); handler(...args) }; return this.on(event, wrap) }
  off(event: string, handler: Function) { const set = this.store.get(event); if (set) set.delete(handler) }
  emit(event: string, ...args: any[]) { const set = this.store.get(event); if (!set) return; for (const h of set) h(...args) }
}

使用与验证要点

  • 防抖与节流需根据交互选择领先/尾随与最大等待
  • 深拷贝与深比较需覆盖 Map/Set/Date/RegExp/TypedArray 与循环引用
  • 并发控制适用于批量请求或上传,避免阻塞与雪崩
  • Promise 工具与事件总线是常见基础设施,便于题目延伸
相关推荐
用户28907942162719 分钟前
Spec-Kit应用指南
前端
能源系统预测和优化研究11 分钟前
创新点解读:基于非线性二次分解的Ridge-RF-XGBoost时间序列预测(附代码实现)
人工智能·深度学习·算法
执笔论英雄16 分钟前
【RL】ROLL下载模型流程
人工智能·算法·机器学习
酸菜土狗17 分钟前
🔥 手写 Vue 自定义指令:实现内容区拖拽调整大小(超实用)
前端
ohyeah21 分钟前
深入理解 React Hooks:useState 与 useEffect 的核心原理与最佳实践
前端·react.js
Cache技术分享22 分钟前
275. Java Stream API - flatMap 操作:展开一对多的关系,拉平你的流!
前端·后端
yaoh.wang22 分钟前
力扣(LeetCode) 100: 相同的树 - 解法思路
python·程序人生·算法·leetcode·面试·职场和发展·跳槽
SadSunset25 分钟前
力扣题目142. 环形链表 II的解法分享,附图解
算法·leetcode·链表
Sunsets_Red1 小时前
2025 FZYZ夏令营游记
java·c语言·c++·python·算法·c#
apollo_qwe1 小时前
前端缓存深度解析:从基础到进阶的实现方式与实践指南
前端