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

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


复盘目标

  • 以可复用的前端实现为主线,覆盖概念、边界与代码
  • 题目聚焦高频场景:防抖、节流、深拷贝、深比较、并发控制、柯里化与组合、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 工具与事件总线是常见基础设施,便于题目延伸
相关推荐
灵感__idea7 小时前
Hello 算法:贪心的世界
前端·javascript·算法
ZK_H8 小时前
嵌入式c语言——关键字其6
c语言·开发语言·计算机网络·面试·职场和发展
澈2078 小时前
深入浅出C++滑动窗口算法:原理、实现与实战应用详解
数据结构·c++·算法
GreenTea8 小时前
一文搞懂Harness Engineering与Meta-Harness
前端·人工智能·后端
ambition202428 小时前
从暴力搜索到理论最优:一道任务调度问题的完整算法演进历程
c语言·数据结构·c++·算法·贪心算法·深度优先
cmpxr_8 小时前
【C】原码和补码以及环形坐标取模算法
c语言·开发语言·算法
qiqsevenqiqiqiqi8 小时前
前缀和差分
算法·图论
代码旅人ing9 小时前
链表算法刷题指南
数据结构·算法·链表
Yungoal9 小时前
常见 时间复杂度计算
c++·算法
fei_sun10 小时前
面经、笔试(持续更新中)
fpga开发·面试