首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >技术面试复盘:高频算法题的前端实现思路(防抖、节流、深拷贝等)

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

作者头像
fruge365
发布2025-12-15 14:03:13
发布2025-12-15 14:03:13
2290
举报

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


复盘目标

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

防抖(Debounce)

代码语言:javascript
复制
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)

代码语言:javascript
复制
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)

代码语言:javascript
复制
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)

代码语言:javascript
复制
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
}

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

代码语言:javascript
复制
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() })
  }
}

柯里化与组合

代码语言:javascript
复制
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 工具

代码语言:javascript
复制
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)

代码语言:javascript
复制
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 工具与事件总线是常见基础设施,便于题目延伸

边界与坑位解析

  • 防抖与节流:表单输入与窗口滚动适配不同策略;leading/trailing/maxWait 组合避免长期不触发;在组件卸载时取消定时器。
  • 深拷贝:访问器属性保持描述符;ArrayBuffer 与 TypedArray 按构造器复制;处理循环引用与原型链。
  • 深比较:针对 Map/Set 的值比较与大小一致性;访问器的结构一致判断而非值比较。
  • 并发控制:任务失败时继续运行队列,避免饿死;保证 resolve 或 reject 都减少活动计数。
  • Promise 工具:retry 注意幂等与副作用;timeout 与 AbortController 联动避免资源泄漏。

复杂度与性能评估

  • 防抖/节流:时间复杂度 O(1)、空间 O(1)。
  • 深拷贝:平均 O(n)、空间 O(n);对大对象建议分层或按需克隆。
  • 深比较:平均 O(n);大对象可用标识或哈希摘要替代。
  • 并发控制:调度开销与队列线性相关;并发窗口建议 4–8。

使用示例

代码语言:javascript
复制
const onInput = debounce((v: string) => doSearch(v), 300, { trailing: true })
const onScroll = throttle(() => checkPosition(), 200)
const cloned = deepClone({ a: new Map([[1, { x: 1 }]]) })
const equal = deepEqual(new Set([1,2]), new Set([2,1]))
const add = createScheduler(4)
add(() => fetch('/a'))
const req = retry(() => fetch('/api').then(r => r.json()), 3, 200)
const bus = new Emitter()
bus.on('ready', () => {})

单元测试片段

代码语言:javascript
复制
import { expect } from 'vitest'
expect(typeof debounce(() => 1, 100)).toBe('function')
expect(deepEqual({a:1}, {a:1})).toBe(true)
expect(deepEqual(new Date(1), new Date(1))).toBe(true)
const s = createScheduler(2)
const order: number[] = []
await Promise.all([
  s(async () => { order.push(1) }),
  s(async () => { order.push(2) }),
  s(async () => { order.push(3) })
])
expect(order[0]).toBe(1)

与现代 API 的对比

  • AbortController:与 timeout、retry 结合做中断与超时。
  • Promise.allSettled:原生已支持,示例提供兼容封装。
  • IntersectionObserver:与并发控制组合用于懒加载与无限滚动。

进阶实现示例

合并防抖与节流
代码语言:javascript
复制
export function debounceThrottle<T extends (...args:any[])=>any>(fn:T, debounceMs=300, throttleMs=100){
  const d = debounce(fn, debounceMs)
  const t = throttle(fn, throttleMs)
  return (...args:any[])=>{ d(...args); t(...args) }
}
超时可中断的请求
代码语言:javascript
复制
export async function fetchWithTimeout(input: RequestInfo, init: RequestInit & { timeout?: number } = {}){
  const ctrl = new AbortController()
  const id = setTimeout(()=>ctrl.abort(), init.timeout ?? 2000)
  try { const res = await fetch(input, { ...init, signal: ctrl.signal }); return res }
  finally { clearTimeout(id) }
}
带优先级的并发队列
代码语言:javascript
复制
type Task = { run: () => Promise<any>; priority: number }
export function createPriorityScheduler(limit=4){
  let active=0
  const q: Task[]=[]
  function schedule(){ q.sort((a,b)=>b.priority-a.priority); while(active<limit && q.length){ const t=q.shift()!; active++; t.run().then(()=>{active--; schedule()}).catch(()=>{active--; schedule()}) } }
  return (run:()=>Promise<any>, priority=0)=>{ q.push({run,priority}); schedule(); return new Promise<void>(r=>{ r() }) }
}

面试答题策略建议

  • 定义与边界先行:明确输入输出、时序与副作用,给出复杂度与资源估算。
  • 逐步完善:先写最小可运行版本,再补全边界、中断、清理与容错。
  • 可测与可复用:提供简短的测试与用例,呈现可复用的封装与组合能力。

场景映射速查表

  • 表单搜索输入:防抖 trailing
  • 滚动事件与窗口调整:节流 leading+trailing 或 raf 节流
  • 大对象复制与比较:深拷贝/深比较,优先分层与标识
  • 批量上传与抓取:并发窗口 + 重试 + 超时 + 取消
  • 组合构建:柯里化、compose/pipe、事件总线

帧级节流(requestAnimationFrame)

代码语言:javascript
复制
export function rafThrottle<T extends (...args:any[])=>any>(fn:T){
  let locked=false
  return (...args:any[])=>{
    if(locked) return
    locked=true
    requestAnimationFrame(()=>{ locked=false; fn(...args) })
  }
}

LRU 缓存(固定容量,最近使用优先)

代码语言:javascript
复制
export class LRU<K,V>{
  private map=new Map<K,V>()
  constructor(private cap:number){}
  get(k:K){ if(!this.map.has(k)) return undefined as any; const v=this.map.get(k)!; this.map.delete(k); this.map.set(k,v); return v }
  set(k:K,v:V){ if(this.map.has(k)) this.map.delete(k); this.map.set(k,v); if(this.map.size>this.cap){ const first=this.map.keys().next().value as K; this.map.delete(first) } }
  has(k:K){ return this.map.has(k) }
  size(){ return this.map.size }
}

异步队列(并发窗口+重试+超时)

代码语言:javascript
复制
type Job<T>=()=>Promise<T>
export function createAsyncPool(limit=4){
  let active=0
  const q: Array<()=>void> = []
  function drain(){ while(active<limit && q.length){ const f=q.shift()!; active++; f() } }
  return function run<T>(job: Job<T>){
    return new Promise<T>((resolve,reject)=>{
      const exec=()=>{ job().then(v=>{ active--; resolve(v); drain() }).catch(e=>{ active--; reject(e); drain() }) }
      q.push(exec); drain()
    })
  }
}
export async function withRetry<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(e){ if(i===times-1) throw e; await new Promise(r=>setTimeout(r,d)); d*=factor } }
  throw new Error('retry failed')
}
export async function withTimeout<T>(p:Promise<T>, ms=2000){ return Promise.race([p,new Promise<T>((_,rej)=>setTimeout(()=>rej(new Error('timeout')),ms))]) }

Promise polyfills(all/any/race)

代码语言:javascript
复制
export function all<T>(arr:Array<T|Promise<T>>){ return Promise.all(arr.map(x=>Promise.resolve(x))) }
export function any<T>(arr:Array<T|Promise<T>>){
  return new Promise<T>((resolve,reject)=>{
    let remaining=arr.length
    const errors:any[]=[]
    arr.forEach(p=>Promise.resolve(p).then(resolve).catch(e=>{ errors.push(e); if(--remaining===0) reject(new AggregateError(errors)) }))
  })
}
export function race<T>(arr:Array<T|Promise<T>>){ return new Promise<T>((resolve,reject)=>{ arr.forEach(p=>Promise.resolve(p).then(resolve,reject)) }) }

structuredClone 优先的安全拷贝

代码语言:javascript
复制
export function safeClone<T>(x:T):T{ const sc=(globalThis as any).structuredClone; return typeof sc==='function' ? sc(x) : deepClone(x as any) }

组合示例(搜索与懒加载)

代码语言:javascript
复制
const pool=createAsyncPool(4)
const search=debounce((q:string)=>pool(()=>withTimeout(fetch(`/api?q=${q}`).then(r=>r.json()),1500)),300,{trailing:true})
const onScroll=rafThrottle(()=>pool(()=>withRetry(()=>fetch('/more').then(r=>r.json()),2,200)))

评测建议与展示方式

  • 以微基准展示时序与触发次数;记录平均延迟与抖动
  • 大对象测试以样本集评估正确性(Map/Set/Date/RegExp/TypedArray)
  • 并发池以任务队列长度与吞吐量、失败重试次数为指标

复杂边界清单

  • 防抖窗口内参数变化与上下文绑定一致性
  • 节流在窗口边界时的二次触发与参数覆盖
  • 深拷贝对 Symbol 键、不可枚举属性与访问器的保留
  • 深比较对 NaN、-0、循环引用与自定义原型的判断
  • 并发池任务失败与中断后计数与队列恢复
  • Promise 聚合在空数组与全失败时的返回结构

React 集成示例

代码语言:javascript
复制
import React, { useMemo, useState } from 'react'
import { debounce, rafThrottle, retry } from './utils'

export default function SearchPage() {
  const [q, setQ] = useState('')
  const debouncedSearch = useMemo(() => debounce((x: string) => fetch(`/api?q=${x}`), 300, { trailing: true }), [])
  const onScroll = useMemo(() => rafThrottle(() => retry(() => fetch('/more'), 2, 200)), [])
  return (
    <div onScroll={onScroll as any}>
      <input value={q} onChange={e => { setQ(e.target.value); debouncedSearch(e.target.value) }} />
    </div>
  )
}

Vue 集成示例

代码语言:javascript
复制
import { ref, onMounted } from 'vue'
import { debounce, rafThrottle } from './utils'
export default {
  setup() {
    const q = ref('')
    const run = debounce((x: string) => fetch(`/api?q=${x}`), 300, { trailing: true })
    const onScroll = rafThrottle(() => fetch('/more'))
    onMounted(() => window.addEventListener('scroll', onScroll))
    return { q, run }
  }
}

复杂度速表

代码语言:javascript
复制
防抖/节流    时间 O(1)    空间 O(1)
深拷贝        时间 O(n)    空间 O(n)
深比较        时间 O(n)    空间 O(n)
并发池        时间 O(k+n)  空间 O(n)

FAQ

  • 何时选防抖而非节流
  • 如何在滚动中降低抖动
  • 为什么 structuredClone 更安全
  • 并发窗口过大导致阻塞的处理
  • 如何在答案中呈现复杂度与边界

检查清单

  • 定时器与中断资源释放
  • 参数与上下文一致
  • 原型链与访问器保留策略
  • 队列失败与恢复逻辑
  • 聚合返回结构与错误栈
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-12-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 技术面试复盘:高频算法题的前端实现思路(防抖、节流、深拷贝等)
    • 复盘目标
    • 防抖(Debounce)
    • 节流(Throttle)
    • 深拷贝(Deep Clone)
    • 深比较(Deep Equal)
    • 并发控制(限制同时执行的任务数)
    • 柯里化与组合
    • Promise 工具
    • 事件总线(EventEmitter)
    • 使用与验证要点
    • 边界与坑位解析
    • 复杂度与性能评估
    • 使用示例
    • 单元测试片段
    • 与现代 API 的对比
    • 进阶实现示例
      • 合并防抖与节流
      • 超时可中断的请求
      • 带优先级的并发队列
    • 面试答题策略建议
    • 场景映射速查表
    • 帧级节流(requestAnimationFrame)
    • LRU 缓存(固定容量,最近使用优先)
    • 异步队列(并发窗口+重试+超时)
    • Promise polyfills(all/any/race)
    • structuredClone 优先的安全拷贝
    • 组合示例(搜索与懒加载)
    • 评测建议与展示方式
    • 复杂边界清单
    • React 集成示例
    • Vue 集成示例
    • 复杂度速表
    • FAQ
    • 检查清单
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档