文档教程前端【免费下载链接】zh.javascript.info现代 JavaScript 教程The Modern JavaScript Tutorial以最新的 ECMAScript 规范为基准通过简单但足够详细的内容为你讲解从基础到高阶的 JavaScript 相关知识。项目地址https://gitcode.com/gh_mirrors/zh/zh.javascript.info点击查看免费下载在 JavaScript 教程仓库zh.javascript.info的装饰器模式和转发call/apply章节中防抖debounce是与透明缓存、节流throttle并列的经典装饰器之一。本文以该章节的防抖任务及其标准解答为主体完整讲解debounce装饰器的行为语义、约 6 行核心实现、this/arguments的转发机制并结合仓库自带的测试用例与实时演示页面验证其正确性。读完本文你将能独立实现一个可复用的防抖装饰器并理解它在输入框、搜索请求等高频事件场景中的价值。防抖装饰器要解决什么问题debounce(f, ms)装饰器的结果是一个包装器wrapper该包装器会暂停对f的调用直到经过ms毫秒的非活动状态即没有任何函数调用的冷却期之后才使用最新一次调用的参数调用f一次。任务文档中给出了一个非常形象的类比debounce就像一个接听电话的秘书它会一直等待直到电话安静了ms毫秒之后才将最新的呼叫信息传达给老板也就是真正调用实际的f。它的行为可以用一句话概括每个调用都会重置计时器只有在最后一次调用之后、持续ms毫秒都没有新调用时才真正执行一次f执行时使用最后一次调用的参数之前所有调用的参数都被忽略。时序示例0ms、200ms、500ms 的三次调用假设我们有一个函数f并将其替换为f debounce(f, 1000)。然后包装函数分别在 0ms、200ms 和 500ms 时被调用之后再也没有其他调用。那么实际的f只会在 1500ms 时被调用一次——也就是从**最后一次调用500ms**开始经过 1000ms 的冷却期之后。任务文档用一张时序图直观地说明了这个过程可以看到0ms 的调用f(a)、200ms 的调用f(b)都因为后续又有新调用而被取消500ms 的调用f(c)是最后一次调用此后 1000ms 内没有新调用于是 1500ms 时执行f(c)最终f只被调用一次并且拿到的是最后一个调用的参数c其余参数被丢弃。这与 Lodash 的防抖行为一致。任务文档展示了使用 Lodash 时的等价写法_.debounce内部同样遵循最后一次调用后等待冷却期的语义let f _.debounce(alert, 1000); f(a); setTimeout( () f(b), 200); setTimeout( () f(c), 500); // 防抖函数从最后一次函数调用以后等待 1000ms然后执行alert(c)标准实现六行核心代码任务要求实现自己的debounce装饰器。仓库给出的标准解答以及对应的源码文件如下function debounce(func, ms) { let timeout; return function() { clearTimeout(timeout); timeout setTimeout(() func.apply(this, arguments), ms); }; }整个实现只有三处关键逻辑我们逐行拆解1. 闭包中保存计时器let timeouttimeout定义在debounce的函数作用域内被返回的包装器通过闭包持续引用。它是整个防抖机制的状态载体每次调用包装器时都能看到并重置上一次调用留下的计时器。2. 每次调用先清除旧计时器clearTimeout(timeout)这是冷却期语义的核心。每一次对包装器的调用都会先取消上一次安排的延迟调用。因此只要新调用到来得足够频繁间隔小于ms之前安排的func调用就永远不会有机会执行只有连续ms毫秒没有任何调用时最后一次安排的任务才会真正落地。3. 重新安排延迟调用并转发上下文timeout setTimeout(() func.apply(this, arguments), ms);在ms毫秒后执行箭头函数其中arguments是本次最后一次调用包装器时传入的参数集合因此最终func拿到的永远是最新参数func.apply(this, arguments)把当前this和全部参数原样转发给原始函数。注意这里对this的处理非常讲究。正如主章节装饰器模式和转发call/apply所强调的如果包装器直接写func(...)调用原始函数内部的this会丢失变成undefined对象方法装饰后会报错。而apply将调用时的上下文一并呼叫转移call forwarding给了func这正是主章节总结的通用转发范式let wrapper function() { return func.apply(this, arguments); };debounce与这个通用包装器的区别只在于它不是立即转发而是延迟到冷却期结束之后转发。使用apply而非call是因为apply直接接受类数组的arguments对象写法最简洁对既是可迭代又是类数组的arguments来说apply也通常更快大多数 JavaScript 引擎内部对其做了优化。测试用例如何验证行为仓库为这个解答配套了完整的测试文件基于 Mocha Sinon 编写共覆盖三个核心行为正好对应任务要求的所有语义用例一单次调用在给定毫秒数之后执行it(for one call - runs it after given ms, function () { const f sinon.spy(); const debounced debounce(f, 1000); debounced(test); assert(f.notCalled, not called immediately); this.clock.tick(1000); assert(f.calledOnceWith(test), called after 1000ms); });验证两点调用包装器后f不会立即执行notCalled计时 1000ms 后才执行且参数正确calledOnceWith(test)。用例二三次调用只执行最后一次it(for 3 calls - runs the last one after given ms, function () { const f sinon.spy(); const debounced debounce(f, 1000); debounced(a); setTimeout(() debounced(b), 200); // ignored (too early) setTimeout(() debounced(c), 500); // runs (1000 ms passed) this.clock.tick(1000); assert(f.notCalled, not called after 1000ms); this.clock.tick(500); assert(f.calledOnceWith(c), called after 1500ms); });这正是时序图中 0ms / 200ms / 500ms 场景的自动化版本1000ms 时距最后一次调用c仅 500msf仍未执行再推 500ms累计 1500ms距c满 1000ms后f恰好被调用一次且参数为c。a与b的调用被完全吞掉。用例三保留调用上下文it(keeps the context of the call, function () { let obj { f() { assert.equal(this, obj); }, }; obj.f debounce(obj.f, 1000); obj.f(test); this.clock.tick(5000); });验证了func.apply(this, arguments)的关键作用当debounce应用于对象方法obj.f后延迟执行时f内部的this依然是obj。如果实现中漏掉this转发比如直接func(...)这个用例就会失败。测试还使用了sinon.useFakeTimers()模拟时钟通过this.clock.tick(...)手动推进时间从而在毫秒级精确验证防抖的时序语义无需真实等待。真实应用场景输入事件防抖防抖最典型的实战场景是用户输入。任务文档给出了具体例子假设用户输入了一些内容我们想在用户输入完成时向服务器发送请求。显然没有必要为每一个字符的输入都发送请求而是希望等一段时间、拿到整个输入结果后再处理。在 Web 浏览器中输入框的input事件处理程序会被非常频繁地调用几乎每次按键都会触发。如果给这个处理程序套上 1000ms 的debounce它就只会在最后一次输入后的 1000ms被调用一次——这正是搜索框输入停顿后自动搜索等功能的底层机制。仓库为此提供了可直接运行的演示页面它并排展示两个输入框形成对照script srchttps://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js/script input idinput1 placeholdertype here !-- 直接调用 handler -- input idinput2 placeholdertype here !-- 调用 debounce(handler, 1000) -- script function handler(event) { result.innerHTML event.target.value; } input1.oninput handler; input2.oninput _.debounce(handler, 1000); /script实际效果是第一个输入框的handler对每次输入即时响应第二个输入框的内容则在最后一次输入 1000ms 之后才被处理。由于debounce只对停顿后的最终状态感兴趣它特别适合处理一系列连续事件——无论是键盘输入、鼠标移动还是其他高频触发的事件。防抖、节流与 Lodash 的关系防抖debounce在一连串调用中只执行最后一次调用后冷却ms毫秒的那一次适合等用户停下来再干活如输入搜索、表单校验。节流throttle保证在固定时间间隔内至多执行一次适合持续事件但要求有规律地处理如滚动、拖拽时的位置更新。仓库中与防抖并列的还有节流任务二者是高频事件优化的姊妹方案。Lodash任务文档明确说明其实现参考了 Lodash 的_.debounce。Lodash 版本还提供leading/trailing、maxWait等增强选项本文手写版本聚焦最核心的延迟合并语义足以覆盖大多数场景。使用时的注意事项返回值问题本实现中包装器不返回func的结果防抖场景中原始函数通常是发请求更新界面这类不依赖返回值的操作。如果原始函数有返回值且调用方依赖它需要另行设计Lodash 的_.debounce同样不会透传最后一次执行的返回值。函数属性丢失正如主章节装饰器和函数属性所提醒的装饰器会用包装器替换原函数原函数上的自定义属性如func.calledCount在装饰后不可见。若必须保留属性需要借助Proxy包装函数见后续 proxy 章节。非活动期的定义冷却期是从最后一次调用起算的而不是从第一次调用起算任何一次新调用都会把执行时间整体向后推迟。总结防抖装饰器是合并高频调用、只处理最终结果的经典方案。其核心只有三行逻辑——每次调用clearTimeout旧计时器、重新setTimeout、到期后用func.apply(this, arguments)转发上下文与最新参数。通过标准解答、源码实现、测试用例与演示页面的相互印证你可以放心地将这套实现用于输入搜索、滚动处理等任何需要等一等再干活的场景也可以继续阅读同一章节的节流任务与主章节装饰器模式和转发加深理解。赞分享文档教程前端【免费下载链接】zh.javascript.info现代 JavaScript 教程The Modern JavaScript Tutorial以最新的 ECMAScript 规范为基准通过简单但足够详细的内容为你讲解从基础到高阶的 JavaScript 相关知识。项目地址https://gitcode.com/gh_mirrors/zh/zh.javascript.info点击查看免费下载相关推荐JavaScript 防抖装饰器Debounce Decorator实现与原理Modern JavaScript Tutorial 习题源码剖析JavaScript 防抖装饰器Debounce Decorator实现与原理Modern JavaScript Tutorial 习题源码剖析 防抖d文档/教程前端Modern JavaScript Tutorial 实战手写 debounce 防抖装饰器与 call/apply 调用转发Modern JavaScript Tutorial 实战手写 debounce 防抖装饰器与 call/apply 调用转发 本篇技术指南以 JavaScr文档/教程前端30-seconds-of-code 详解用 JavaScript 实现防抖函数Debounce30 seconds of code 详解用 JavaScript 实现防抖函数Debounce 防抖Debouncing 是前端开发中限制函数调用频教程文档上一篇SSHwifty基于Web的SSH/Telnet管理工具下一篇LittleLink 项目使用教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?