前几天看到一篇文章,个人公众号里也分享了《一次发现underscore源码bug的经历以及对学术界拿来主义的思考》具体文章详见,微信公众号:
文中讲了你们对throttle和debounce存在误解,同时提到了《高程3》中实现节流方法存在一些问题,为了更好的理解这两个概念,搜了不少相关文章,详见文章底部。javascript
throttle与debounce是两个相似的概念,目的都是随着时间的推移控制执行函数的次数,可是有些细微的差异。css
当咱们为DOM事件关联方法时,若咱们有一个debounced和throttled函数将会很方便,为什么?由于这样咱们能够在事件和执行函数之间添加一层控制,注意咱们并无去控制DOM事件触发的次数。html
例如,咱们谈一下scroll事件,看下面的例子:html5
<p data-height="268" data-theme-id="0" data-slug-hash="xVpoOe" data-default-tab="result" data-user="ghostcode" class="codepen">See the Pen Scroll events counter by ghostcode (@ghostcode) on CodePen.</p>java
当你在触控板或者鼠标滚动时,每次最少会达到30次,在手机上更多。但是你的滚动事件处理函数对这个频率是否应付的过来?node
在2011年,Twitter网站曾爆出一个问题:当你在主页往下滚动时,页面会变得缓慢以至没有响应。John Resig发表了一篇文章《 a blog post about the problem》指出直接在scroll事件上面绑定高消耗的事件是一个多么愚蠢的想法。jquery
在那个时候John建议使用一个独立于scroll事件且每250ms执行的轮询方法。这样的话处理方法就不会耦合于事件。经过这个简单的技术,咱们能够提升用户体验。git
如今有一些更先进的事件处理方法,让我来给你介绍:__Debounce,Throttle和requestAnimationFrame__,同时会介绍一些适用的场景。github
Debouncenpm
Debounce技术使咱们能够将一个连续的调用归为一个。
想象你在电梯的场景,当电梯门开始要关闭的时候,忽然一我的进来,此时电梯并不会关闭而且也不会执行改变楼层的方法,若是还有人进来一样的事情会发生:电梯延迟执行它的方法(改变楼层),优化了它的资源。
本身尝试一下,在按钮上点击或者移动鼠标:
<p data-height="268" data-theme-id="0" data-slug-hash="vGpqLO" data-default-tab="result" data-user="ghostcode" class="codepen">See the Pen Debounce. Trailing by ghostcode (@ghostcode) on CodePen.</p>
你能够看到快速连续的事件是如何经过一个debounce事件来表示的。
Leading edge (or "immediate")
你能够发现事件结束的时候,debounce的事件并无当即执行而是等待了一些时间才触发。为什么不当即触发,就像开始没有使用debounce事件处理?直到在连续执行的事件中有一个暂停,才会再次触发。
你能够经过一个__leading__的参数作到:
在underscore.js中,这个参数叫immediate。
本身尝试一下:
<p data-height="268" data-theme-id="0" data-slug-hash="VaQwRm" data-default-tab="result" data-user="ghostcode" class="codepen">See the Pen Debounce. Leading by ghostcode (@ghostcode) on CodePen.</p>
Debounce Implementations
2009年在John Hann的文章中第一次看到debounce的实现方法。
在那以后不久,Ben Alman写了一个jQuery插件(如今不在维护),一年之后Jeremy Ashkenas把此方法添加到underscore.js中,不久又被添加到lodash中。
<p data-height="268" data-theme-id="0" data-slug-hash="GZQRLv" data-default-tab="result" data-user="ghostcode" class="codepen">See the Pen debounce-click by ghostcode (@ghostcode) on CodePen.</p>
这三种实现方法内部不一样,可是接口几乎一致。
有段时间underscore采用了Lodash的实现方法,可是在我发现了一个bug以后,自此两个库的实现开始分道扬镳。
Lodash在_.debounce和_.throttle中添加了许多特性。immediate标示替代了leading和trailing。你能够二选一或者都选,默认状况下,只有trailing是开启的。
Debounce Examples
当改变浏览器窗口时,resize事件会触发屡次。
<p data-height="268" data-theme-id="0" data-slug-hash="PNQorE" data-default-tab="result" data-user="ghostcode" class="codepen">See the Pen Debounce Resize Event Example by ghostcode (@ghostcode) on CodePen.</p>
如你所见,咱们使用了__trailing__参数,由于咱们只对用户中止改变浏览器大小时最后一次事件感兴趣。
AutoComplete中的Ajax请求使用的keypress
当用户仍旧在输入的时候,为什么每隔50ms发送Ajax请求?__ _.debounce __能够帮助咱们避免额外的工做,只在用户中止输入的时候发送请求。
<p data-height="268" data-theme-id="0" data-slug-hash="wGyvVj" data-default-tab="result" data-user="ghostcode" class="codepen">See the Pen Debouncing keystrokes Example by ghostcode (@ghostcode) on CodePen.</p>
另外一个使用场景是在进行input校验的时候,“你的密码过短”等相似的信息。
如何使用debounce和throttle以及常见的陷阱?
能够本身实现这两个方法或者随便复制别人blog中的实现方法,个人建议是直接使用underscore和lodash中的方法。若是你只须要这两个方法,能够定制输出lodash方法:
npm i -g lodash-cli lodash-cli include=debounce,throttle
一个常见的陷阱:
// WRONG $(window).on('scroll', function() { _.debounce(doSomething, 300); }); // RIGHT $(window).on('scroll', _.debounce(doSomething, 200));
debounce方法赋值给一个变量以后容许咱们调用一个私有方法:__debounced_version.cancel()__:
var debounced_version = _.debounce(doSomething, 200); $(window).on('scroll', debounced_version); // If you need it debounced_version.cancel();
Throttle
使用__ _.throttle __,咱们不容许方法在每Xms间执行超过一次。
和debounce的主要区别是throttle保证方法每Xms有规律的执行。
Throttling Examples
一个至关常见的例子,用户在你无限滚动的页面上向下拖动,你须要判断如今距离页面底部多少。若是用户快接近底部时,咱们应该发送请求来加载更多内容到页面。
在此__ _.debounce 没有用,由于它只会在用户中止滚动时触发,但咱们须要用户快到达底部时去请求。经过 _.throttle __咱们能够不间断的监测距离底部多远。
<p data-height="268" data-theme-id="0" data-slug-hash="xVYbGZ" data-default-tab="result" data-user="ghostcode" class="codepen">See the Pen Infinite scrolling throttled by ghostcode (@ghostcode) on CodePen.</p>
requestAnimationFrame (rAF)
requestAnimationFrame是另外一个频率限制的方法。
它能够经过__ _.throttle(dosomething, 16)__实现,但为了更加精准浏览器提供了内置API。
咱们可使用rAF API做为throttle方法的替代,考虑一下利弊:
利:
弊:
根据经验,我建议在JS执行"painting"或"animating"中直接操做属性和从新计算元素位置时使用rAF。
发送Ajax请求或者是否添加/删除class(触发一个CSS动画)时,我会考虑debounce和throttle,此时你能够下降执行频率(200ms而不是16ms)。
rAF的例子
在Paul Lewis的文章激发下,我只在scroll事件中提供例子。
我一步步的调throttle到16ms,但愿给一个相似的体验,可是rAF在复杂场景下或许会提供更好的结果。
<p data-height="268" data-theme-id="0" data-slug-hash="qZxEaq" data-default-tab="result" data-user="ghostcode" class="codepen">See the Pen Scroll comparison requestAnimationFrame vs throttle by ghostcode (@ghostcode) on CodePen.</p>
一个更好的例子我是在headroom.js中看到的,这里经过一个对象封装,进行了逻辑解藕。
总结:
使用debounce,throttle和requestAnimationFrame优化你的事件处理函数。每个方法有一些细微的差异,三个都颇有用并且互相弥补。