前端进阶之旅前端进阶之旅
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
首页程序员面试题库防抖与节流
JSJavaScript手写与交互性能

防抖和节流有什么区别?

尾沿防抖等连续操作停下来再执行;节流在持续操作中限制执行频率。搜索联想和滚动采样,适合不同的策略。

前端进阶之旅 · 一题精讲更新于 2026.09.05
JavaScript#手写与交互性能#性能优化
先看核心答案读代码示例
理解线索

同样是连续操作,目标不同

  1. 连续输入n → ne → next
  2. debounce停下来后,搜索最终输入
  3. throttle操作持续时,间隔提供反馈

限制调用次数,不等于解决请求返回乱序

核心回答

先记住这个答案

防抖把一段连续调用合并,常见的尾沿防抖会在最后一次调用后等待指定时间再执行;再次触发就重新计时。节流限制执行频率,连续触发时仍会按策略间隔执行。搜索联想常用防抖,滚动位置采样常用节流;是否立即执行、是否补最后一次,都需要单独约定。

  • 防抖:停止触发后再执行
  • 节流:持续触发时限制频率
  • 先说清首沿、尾沿和取消规则

防抖和节流的区别,先用时间线说明

假设调用发生在 0、80、160、400ms,等待间隔是 200ms。尾沿防抖的前三次调用被合并,计划在 360ms 执行;400ms 的新调用再计划于 600ms 执行。这里写的是理想调度时间,主线程繁忙时实际执行可能更晚。

若采用只执行首沿的节流,0ms 立即执行,80ms 和 160ms 被忽略,400ms 再执行。若增加尾沿补发,结果会不同。因此面试时先声明 leading 和 trailing 策略,否则两份看似不同的输出可能都符合各自的实现。

搜索联想 / 输入校验
通常更关心停下来后的最终输入,用尾沿防抖减少中间请求。持续输入不能无限延后时,可设计 maxWait 上限。
滚动位置 / 拖动采样
需要操作过程中持续反馈,用节流限制执行频率;与屏幕动画同步的更新还应考虑 requestAnimationFrame。

手写防抖:保留 this、参数与 cancel

下面实现的是尾沿 debounce:每次调用取消旧定时器并重新安排。返回普通函数保留调用方的 this,定时器回调用箭头函数捕获这次调用的 this 和 args。cancel 用于页面离开或交互撤销时清理待执行任务。

验证“防抖与节流”中的“手写防抖:保留 this、参数与 cancel”时,应固定输入、运行环境与触发顺序,并同时记录正常结果和失败结果。这样能确认本节结论来自目标机制,而不是缓存、旧状态或偶然时序。

尾沿防抖 · 支持取消JavaScript
function debounce(fn, wait) {
  let timer;
  function debounced(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => {
      timer = undefined;
      fn.apply(this, args);
    }, wait);
  }
  debounced.cancel = () => {
    clearTimeout(timer);
    timer = undefined;
  };
  return debounced;
}

const search = debounce(query => console.log(query), 200);
search('n');
search('ne');
search('next');
查看输出与解释
next

同步连续调用只留下最后一个定时器,至少等待指定时间后打印 next。调用 search.cancel() 可取消尚未执行的这次任务;此版本不提供延迟函数的同步返回值。

手写节流:先明确首沿执行策略

下面的 throttle 用 performance.now() 测量时间间隔,首次立即执行,间隔内忽略新调用,不补尾沿。它适合讲清核心思路;如果停止操作后必须更新最后一个位置,需要扩展尾沿逻辑,不能把这个版本直接当作所有场景的完整实现。

验证“防抖与节流”中的“手写节流:先明确首沿执行策略”时,应固定输入、运行环境与触发顺序,并同时记录正常结果和失败结果。这样能确认本节结论来自目标机制,而不是缓存、旧状态或偶然时序。

首沿节流 · 不补发最后一次JavaScript
function throttle(fn, wait) {
  let lastTime = -Infinity;
  return function throttled(...args) {
    const now = performance.now();
    if (now - lastTime >= wait) {
      lastTime = now;
      return fn.apply(this, args);
    }
  };
}

const report = throttle(value => console.log(value), 200);
report('first');
report('second');
查看输出与解释
first

正常连续执行且间隔小于 200ms 时,只输出 first。这里没有尾沿定时器,停止调用也不会自动补发 second;若调试断点使两次调用相隔足够久,则两次都会执行。

实际使用还要处理生命周期与请求竞态

不要在每次 input 事件里重新调用 debounce 创建包装函数,那样每次都有独立 timer,旧任务无法被清除。在 React 中也要让同一段交互复用包装函数,同时处理回调捕获旧状态的问题;组件卸载或依赖切换时取消仍在等待的调用。

防抖只减少发起请求的次数,不能保证响应按发出顺序返回。如果旧查询较慢、新查询较快,旧响应仍可能覆盖新结果。可以用递增请求序号判断返回结果是否仍有效,或者通过 AbortController 取消已经过时的请求;cancel 定时器不会自动取消已发出的网络请求。

回答前,多想一步

容易答错的地方

“防抖总是执行最后一次”
这是常见尾沿策略的表现;首沿防抖会先执行。说明 leading、trailing 和 maxWait,比只背“最后一次”更准确。
“节流每隔 wait 必定执行”
它约束的是调用频率,不是独立运行的时钟。没有新触发且没有尾沿任务时,不会凭空执行;真实计时器也可能延迟。
“减少请求就解决了竞态”
发起频率和返回顺序是两个问题。输入变化后,已经发出的旧请求还需要取消或在写入结果时进行有效性检查。
试着用自己的话回答

面试官还会怎么问?

为什么防抖返回的函数不能直接写成箭头函数?

如果需求是透传调用方的 this,返回普通函数可以接收调用时的 this;返回箭头函数则会捕获创建它的外层 this,调用方通过对象方法或 call 传入的新 this 不会生效。若原回调完全不依赖 this,也可以使用明确不支持 this 透传的简化版本。

cancel 和 flush 有什么区别?

cancel 丢弃尚未执行的尾沿任务;flush 则立即执行已经等待中的调用,通常还要返回结果。本页防抖仅实现 cancel。如果要加 flush,需要额外保存最后的参数和上下文,处理没有待执行任务、重复调用和清理状态的边界。

一直输入时,怎样保证最终会触发搜索?

单纯尾沿防抖可能被持续输入无限推迟。如果交互要求持续输入期间也周期性得到结果,可引入 maxWait 限制最长等待时间,或选择合适的节流策略。需求应同时定义首次反馈、最后一次输入和最长等待三个条件。

从一道题,走向一组知识

把知识连起来

作用域与函数

JavaScript 闭包是什么?

理解多次调用为什么能共享同一个 timer。

异步与浏览器

JavaScript 事件循环如何执行?

解释为什么计时器到点了,回调也不一定立即执行。

放回知识体系中复习JavaScript 手写题:防抖与节流

参考资料

  • MDN · Debounce
  • MDN · Throttle

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 防抖和节流的区别,先用时间线说明
  3. 手写防抖:保留 this、参数与 cancel
  4. 手写节流:先明确首沿执行策略
  5. 实际使用还要处理生命周期与请求竞态
  6. 容易答错的地方
  7. 面试官还会怎么问
  8. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑