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

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


复盘目标

  • 以可复用的前端实现为主线,覆盖概念、边界与代码
  • 题目聚焦高频场景:防抖、节流、深拷贝、深比较、并发控制、柯里化与组合、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 工具与事件总线是常见基础设施,便于题目延伸
相关推荐
考研保研资料分享16 分钟前
2022旅游管理跨保华东师大人文地理:五场面试与择校复盘
面试·旅游
你挚爱的强哥27 分钟前
如何设置 Chrome 打开默认页面不要是360的主页https://hao.360.com/?src=lm&ls=n6205869099
前端·chrome·360首页劫持
总要学点什么32 分钟前
第75篇:右键菜单控件——把一张会做事的菜单钉在鼠标落下的那点
前端·信息可视化·echarts·cesium·空间分析
计算机魔术师37 分钟前
国办说AI要"缓"着上,但你公司可能已经全上完了
前端
lisw051 小时前
分岔论揭示演化突变密码
开发语言·算法·数学建模
IT_陈寒1 小时前
SpringBoot性能优化完全指南
前端·人工智能·后端
aqiu1111111 小时前
C++ TopK:使用 nth_element 快速求最小 K 个数(蓝桥杯 / LeetCode 可用)
算法
Suprman1 小时前
平面排版问题的数学建模与算法设计
算法·数学建模·排序算法·排版套料
FPGA信号处理1 小时前
【应用随机过程】:更新过程与马尔可夫链入门
算法·概率论
溪语流沙1 小时前
【每天一个CSS | Day33】聚焦即弹出的命令面板
前端·css·html5·组件·每日一练