30-seconds-of-interviews 防抖与节流终极指南:从 8 行 debounce 到生产级实现
发布时间:2026/11/4 2:06:44 锦皓数字建站

30-seconds-of-interviews 防抖与节流终极指南从 8 行 debounce 到生产级实现【免费下载链接】30-seconds-of-interviewsA curated collection of common interview questions to help you prepare for your next interview.项目地址: https://gitcode.com/gh_mirrors/30/30-seconds-of-interviews本文以 30-seconds-of-interviews 仓库中的防抖Debouncing面试题为骨架讲透防抖的原理、标准实现、生产级增强与高频追问。这个仓库是一个精选面试题集配套网站支持按标签过滤与难度排序题目文档存放在questions/目录。防抖是其中 JavaScript 标签下的高频题也是面试区分度很高的一道背得出 8 行实现的人很多能讲清为什么先 clearTimeout 再 setTimeout、能现场写出 leading edge 和 cancel 版本的人很少。一个搜索框里的 5 个请求问题是怎么发生的先看一个真实场景。搜索框每次按键都直接请求接口searchInput.addEventListener(input, (e) { fetch(/api/search?q${encodeURIComponent(e.target.value)}) .then(res res.json()) .then(renderResults); });用户输入 react 会触发 5 次input事件、发出 5 个请求。更糟的是竞态查 r 的请求慢返回时间却晚于查 react 的请求renderResults会把正确结果覆盖成旧结果。服务端日志里则是一堆前缀残缺的查询。questions/debouncing.md 的原文给出了这道题的定性A raw DOM event listeners can easily trigger 20 events per second. A debounced function will only be called once the delay has passed.裸事件监听器每秒可触发 20 次以上事件是浏览器按自己的节奏派发的你的代码无法阻止它只能决定收到后多久执行。防抖的答案是每次事件到来都重置一个定时器只有事件停火且持续满 delay 毫秒后才真正调用一次目标函数。一段密集事件因此被压缩成最后一次触发后的单次回调——请求不再堆积最后一次输入也天然成为唯一生效的状态竞态随之消失。8 行最小实现逐行拆解clearTimeout 就是全部秘密原文档的标准实现如下原文示例里的window.addEventListere是拼写错误应为addEventListener面试白板复述时别带上去const debounce (func, delay) { let debounceTimer; // 定时器句柄闭包持有跨调用存活 return function() { const context this; // 记录调用现场的 this const args arguments; // 记录调用现场的参数 clearTimeout(debounceTimer); // 撤销上一次没跑到的定时器 debounceTimer setTimeout(() func.apply(context, args), delay); }; }; window.addEventListener(scroll, debounce(function() { // 用户停止滚动 1 秒后才执行一次 }, 1000));逐行看它解决了什么问题let debounceTimer声明在返回函数之外、靠闭包保存。它是上次注册的那个定时器的唯一句柄——没有它就没有重置能力防抖无从谈起。clearTimeout(debounceTimer)这就是防抖二字的字面机制。只要事件还在高频到来定时器永远被撤销、永远到不了期函数就一次都不执行事件一停火最后注册的定时器才走完。setTimeout(..., delay)把真正的执行推迟到安静期之后。delay 取多长取决于业务搜索输入常用 300msscroll/resize重排常用 1000ms 量级。context/args两个变量保存普通函数调用现场的this与arguments最后用func.apply(context, args)透传给原函数。防抖包装因此不改变原函数的调用语义——这一点后面高频追问部分会展开。外层debounce(func, delay)、内层function()debounce只执行一次、创建一个包之后所有触发共享同一个debounceTimer。如果误把包装逻辑写进每次事件里新建的函数定时器互相隔离防抖直接失效调试技巧见后文。生产级增强leading edge、cancel 与 flush背出上面的实现只是及格线。面试官追问实际项目里还差什么时这三个点是标准答案每个都给出可运行版本。1. leading edge首次立即执行。标准实现是最后一次触发后执行trailing edge但点击立即提交、延迟期内忽略连点这类场景要求首次触发立即执行const debounce (func, delay, immediate false) { let timer; return function(...args) { const callNow immediate !timer; // 处于冷却期timer 存在则不再执行 clearTimeout(timer); timer setTimeout(() { timer null; // 冷却期结束标志复位 if (!immediate) func.apply(this, args); // trailing到期才执行 }, delay); if (callNow) func.apply(this, args); // leading首次立即执行 }; };这里用定时器是否存在!timer充当冷却期标志immediate true时首次调用立即执行并置位 timer直到 delay 结束后timer null下一次触发才再次立即执行。2. cancel 与 flush挂起执行的控制权。组件卸载时未执行的定时器若不清理回调可能操作已卸载的 DOMconst createDebounced (func, delay) { let timer; let lastArgs; const debounced function(...args) { clearTimeout(timer); lastArgs args; timer setTimeout(() { timer null; const argsToUse lastArgs; // 始终用最后一次调用的参数 lastArgs undefined; func.apply(this, argsToUse); }, delay); }; debounced.cancel () { // 撤销挂起的执行 clearTimeout(timer); timer null; lastArgs undefined; }; debounced.flush () { // 若还有挂起调用立即执行最后一次 if (timer) debounced.cancel(); if (lastArgs) func.apply(this, lastArgs); }; return debounced; }; const onResize createDebounced(layout, 200); window.addEventListener(resize, onResize); // 组件卸载时onResize.cancel();面试讲到这一步意味着你把它当过生产组件而不是笔试题能说出cancel 用在卸载清理、flush 用在强制落一次最终状态就是工程经验的分水岭。高频追问三连防抖 vs 节流、箭头函数陷阱、调试技巧追问 1防抖和节流的区别这是几乎必跟的问题答完框架再给例子防抖 debounce节流 throttle执行时机最后一次触发后等 delay 再执行一次固定间隔内最多执行一次高频输入下的表现完全不执行安静后才执行 1 次持续以固定频率执行典型场景搜索输入发请求、resize 重排滚动进度、拖拽、游戏循环一句话合并窗口内的多次调用限制执行频率的上限追问 2外层为什么用普通函数而不是箭头函数因为要捕获动态的this和参数const debounced debounce(function() { console.log(this thisEl, arguments[0]); }, 1000); const thisEl document.getElementById(app); thisEl.addEventListener(click, debounced, false); // 点击时打印 true 与事件对象参数事件监听器回调的this是元素本身包装层必须把它和arguments原样透传给原函数若外层写成箭头函数this会被词法绑定到定义处透传语义直接丢失。而内层传给setTimeout的回调用箭头函数恰恰是为了让func.apply(context, args)引用的context、args留在闭包里不被外层调用覆盖。两层内外有别是这道题的隐藏考点。追问 3怎么确认防抖真的生效最快的验证是打时间戳看触发次数searchInput.addEventListener(input, debounce((e) { console.log(performance.now().toFixed(0), e.target.value); }, 300));连续输入后应只看到一条日志、且内容是全量文本。如果每个字符都打一条99% 是防抖包装被写进了每次事件回调内部、每次新建了实例——把包装提到事件注册之外即可。一页速览怎么答这道题 题目在仓库里如何流转答题按四层递进一句话定义把多次连续调用合并成一次延迟执行→ 白板实现并解释clearTimeout/setTimeout与闭包的关系 → 典型场景输入停顿后才发 API 请求顺带点出竞态消除→ 主动抛出 leading/trailing、cancel 与防抖 vs 节流。覆盖原文档全部要点又展示工程深度。仓库侧这道题走的是题目—解析—数据—展示管线questions/debouncing.md 是内容源scripts/extract.js 借助 scripts/util.js 的getSection把#### Answer、#### Good to hear、##### Additional links各章节连同 tags、expertise 解析后写入 data/questions.jsonwebsite/js/components/Question.js 与 website/js/components/Filter.js 基于该 JSON 渲染题目并支持按标签过滤。有一个可观察的细节questions/目录已新增的题目尚未全部重新生成进questions.jsondebouncing 正不在其中——面试时不必提但能看出该仓库的数据是生成物而非手工维护的改题目必须重跑 extract 脚本。机制核心clearTimeout重置 setTimeout延迟事件停火满 delay 才执行一次闭包作用debounceTimer跨调用存活是重置能力的前提透传要点保存this与argumentsfunc.apply(context, args)不改变原函数调用语义生产增强leading edge!timer标志、cancel卸载清理、flush强制落最终状态高频追问防抖 vs 节流、外层普通函数 vs 内层箭头函数验证手法performance.now()打时间戳输入停火后只应出现一条日志【免费下载链接】30-seconds-of-interviewsA curated collection of common interview questions to help you prepare for your next interview.项目地址: https://gitcode.com/gh_mirrors/30/30-seconds-of-interviews创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。