throttle与debounce的区别

注:文章中有例子无法在微信里面展示,最好看原文。

以前写过一篇文章《“节流函数”提高性能》,里面讲到用函数“节流”来减少执行次数(不影响体验的情况下),其实实现的代码并没有问题,但是第二个方法的函数名有问题。前几天看到一篇文章,我的公众号里也分享了《一次发现underscore源码bug的经历以及对学术界拿来主义的思考》具体文章详见。

文中讲了大家对throttle和debounce存在误解,同时提到了《高程3》中实现节流方法存在一些问题,为了更好的理解这两个概念,搜了很多相关文章,详见文章底部。

throttle与debounce是两个类似的概念,目的都是随着时间的推移控制执行函数的次数,但是有些细微的差别。

当我们为DOM事件关联方法时,若我们有一个debounced和throttled函数将会很方便,为何?因为这样我们可以在事件和执行函数之间添加一层控制,注意我们并没有去控制DOM事件触发的次数。

例如,我们谈一下scroll事件,看下面的例子:

当你在触控板或者鼠标滚动时,每次最少会达到30次,在手机上更多。可是你的滚动事件处理函数对这个频率是否应付的过来?

在2011年,Twitter网站曾爆出一个问题:当你在主页往下滚动时,页面会变得缓慢以致没有响应。John Resig发表了一篇文章《 a blog post about the problem》指出直接在scroll事件上面绑定高消耗的事件是一个多么愚蠢的想法。

在那个时候John建议使用一个独立于scroll事件且每250ms执行的轮询方法。这样的话处理方法就不会耦合于事件。通过这个简单的技术,我们可以提高用户体验。

现在有一些更先进的事件处理方法,让我来给你介绍:Debounce,Throttle和requestAnimationFrame,同时会介绍一些适用的场景。

Debounce

Debounce技术使我们可以将一个连续的调用归为一个。

想象你在电梯的场景,当电梯门开始要关闭的时候,突然一个人进来,此时电梯并不会关闭并且也不会执行改变楼层的方法,如果还有人进来同样的事情会发生:电梯延迟执行它的方法(改变楼层),优化了它的资源。

自己尝试一下,在按钮上点击或者移动鼠标:

你可以看到快速连续的事件是如何通过一个debounce事件来表示的。

Leading edge (or “immediate”)

你可以发现事件结束的时候,debounce的事件并没有立即执行而是等待了一些时间才触发。为何不立即触发,就像开始没有使用debounce事件处理?直到在连续执行的事件中有一个暂停,才会再次触发。

你可以通过一个leading的参数做到:

在underscore.js中,这个参数叫immediate。

自己尝试一下:

Debounce Implementations

2009年在John Hann的文章中第一次看到debounce的实现方法。

在那之后不久,Ben Alman写了一个jQuery插件(现在不在维护),一年以后Jeremy Ashkenas把此方法添加到underscore.js中,不久又被添加到lodash中。

这三种实现方法内部不同,但是接口几乎一致。

有段时间underscore采用了Lodash的实现方法,但是在我发现了一个bug之后,自此两个库的实现开始分道扬镳。

Lodash在.debounce和.throttle中添加了许多特性。immediate标示替代了leading和trailing。你可以二选一或者都选,默认情况下,只有trailing是开启的。

Debounce Examples

当改变浏览器窗口时,resize事件会触发多次。

如你所见,我们使用了trailing参数,因为我们只对用户停止改变浏览器大小时最后一次事件感兴趣。

AutoComplete中的Ajax请求使用的keypress

当用户仍旧在输入的时候,为何每隔50ms发送Ajax请求? _.debounce 可以帮助我们避免额外的工作,只在用户停止输入的时候发送请求。

另一个使用场景是在进行input校验的时候,“你的密码太短”等类似的信息。

如何使用debounce和throttle以及常见的陷阱?

可以自己实现这两个方法或者随便复制别人blog中的实现方法,我的建议是直接使用underscore和lodash中的方法。如果你只需要这两个方法,可以定制输出lodash方法:

12

npm i -g lodash-clilodash-cli include=debounce,throttle

一个常见的陷阱:

1234567

// WRONG$(window).on('scroll', function() { _.debounce(doSomething, 300); });// RIGHT$(window).on('scroll', _.debounce(doSomething, 200));

debounce方法赋值给一个变量之后允许我们调用一个私有方法:debounced_version.cancel()

12345

var debounced_version = _.debounce(doSomething, 200);$(window).on('scroll', debounced_version);// If you need itdebounced_version.cancel();

Throttle

使用 _.throttle ,我们不允许方法在每Xms间执行超过一次。

和debounce的主要区别是throttle保证方法每Xms有规律的执行。

Throttling Examples

一个相当常见的例子,用户在你无限滚动的页面上向下拖动,你需要判断现在距离页面底部多少。如果用户快接近底部时,我们应该发送请求来加载更多内容到页面。

在此 _.debounce 没有用,因为它只会在用户停止滚动时触发,但我们需要用户快到达底部时去请求。通过_.throttle 我们可以不间断的监测距离底部多远。

requestAnimationFrame (rAF)

requestAnimationFrame是另一个频率限制的方法。

它可以通过 _.throttle(dosomething, 16)实现,但为了更加精准浏览器提供了内置API。

我们可以使用rAF API作为throttle方法的替代,考虑一下利弊:

利:

  • 目标60fps(16ms每贞),但是内部使用最优的时间间隔来渲染
  • 使用简单并且是标准API,以后不会变动,不需要维护

弊:

  • rAF的开始或者取消需要我们自己处理,不像.debounce和.throttle内部实现
  • 浏览器Tag没有激活,它就不会执行
  • 即使多数现代浏览器支持,但是IE9,Opera Mini以及老版本Android依旧不支持。A polyfill到现在依旧需要
  • rAF在node.js中不支持

根据经验,我建议在JS执行”painting”或”animating”中直接操作属性和重新计算元素位置时使用rAF。

发送Ajax请求或者是否添加/删除class(触发一个CSS动画)时,我会考虑debounce和throttle,此时你可以降低执行频率(200ms而不是16ms)。

rAF的例子

在Paul Lewis的文章激发下,我只在scroll事件中提供例子。

我一步步的调throttle到16ms,希望给一个类似的体验,但是rAF在复杂场景下或许会提供更好的结果。

一个更好的例子我是在headroom.js中看到的,这里通过一个对象封装,进行了逻辑解藕。

总结: 使用debounce,throttle和requestAnimationFrame优化你的事件处理函数。每一个方法有一些细微的差别,三个都很有用而且互相弥补。

  • debounce:把突然涌进的事件(键盘事件)归位一个
  • throttle:保证持续执行方法分隔为每Xms执行一次。就像每200ms监测滚动位置来触发css动画。
  • requestAnimationFrame:throttle的替代方案,当你的方法需要重新计算和渲染元素同时你需要更平滑的变动或动画。注意:IE9- 不支持。
  1. https://blog.coding.net/blog/the-difference-between-throttle-and-debounce-in-underscorejs
  2. https://css-tricks.com/the-difference-between-throttling-and-debouncing/
  3. http://stackoverflow.com/questions/25991367/difference-between-throttling-and-debouncing-a-function
  4. http://demo.nimius.net/debounce_throttle/

原文发布于微信公众号 - 前端黑板报(FeHeiBanBao)

原文发表时间:2016-04-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Google Dart

AngularDart4.0 英雄之旅-教程-04明细 顶

在此页面中,您将扩展“Tour of Heroes”应用程序,以显示英雄列表,并允许用户选择英雄并显示英雄的详细信息。

1203
来自专栏静晴轩

Webpack 打包优化之体积篇

谈及如今欣欣向荣的前端圈,不仅有各类框架百花齐放,如Vue, React, Angular等等,就打包工具而言,发展也是如火如荼,百家争鸣;从早期的王者Brow...

4414
来自专栏况鹰的专栏

关于Android图片资源瘦身的奇思妙想

替换掉系统的压缩算法。如果你不嫌麻烦和喜欢折腾的话可以在你的apk使用一下,效果还是非常显著的。

2.5K4
来自专栏magicsoar

微信储存数据的分析

iphone上微信聊天记录的储存分析 由于隐私的原因,这里不能将自己的聊天记录奉献出来 设备:越狱后的iphone5 ios7.0.4            微...

44610
来自专栏糊一笑

【react】开发一款城市选择组件

8013
来自专栏张戈的专栏

利用JS生成二维码图片,优化WEB性能及页面加载速度

移动互联网的蓬勃发展绝对离不开二维码的“推波助澜”,一张小小的图片,省去了繁琐的苦逼输入,也拉近了 PC 端和移动端的距离!虽然是东洋人最初发明的,但我还是要给...

4044
来自专栏程序员的知识天地

如何阅读大型前端开源项目的源码,授人以鱼不如授人以渔

目前网上有很多「XX源码分析」这样的文章,不过这些文章分析源码的范围有限,有时候讲的内容不是读者最关心的。同时我也注意到,源码是在不断更新的,文章里写的源码往往...

4491
来自专栏带你撸出一手好代码

把需求变化带来的代码修改成本降至最低的一种方法

为解决工作中一些繁琐的问题, 写了一个GUI程序, 操作界面是这个样子的 ? 这个程序的实现起来并不是非常的繁琐, 但在界面的交互操作上, 也不仅仅只是展示数据...

3417
来自专栏编程

MVC时代的终结,接下来的函数式响应型编程会成为未来的霸主?

React.js,Elm,Cycle.js和其他UI框架引入了一种构建用户界面的新方法。通过将函数式响应型编程的原理应用于UI开发中,他们甚至改变了我们对用户界...

20010
来自专栏有趣的django

Django+xadmin打造在线教育平台(十)

代码 github下载 十四、xadmin的进阶开发 14.1.权限管理 (1)用户权限 超级用户拥有所有权限,其它添加的用户默认没有任何权限 进后台添加一个...

1.1K11

扫码关注云+社区

领取腾讯云代金券