经过例子解释去抖和节流函数

本文翻译自Debouncing and Throttling Explained Through Examplesjavascript

下面这篇文章是David Corbacho一位伦敦的前端开发工程师写的。咱们以前谈到过这个话题,可是此次,David将举出各类生动的例子来让概念更加容易理解。css

去抖节流是两种相似的(可是不一样!)的技术,它们用来控制随着时间变化咱们容许一个函数被执行的次数。html

当咱们将一个函数绑定做为DOM事件的处理器时使用去抖或者节流版本的函数尤为管用。为何?由于这样作咱们就给本身在事件与函数执行之间添加了一个控制层。记住,咱们不会去控制DOM事件即将会被触发多少次,事件触发的次数是会变化的。前端

举个例子,让咱们来看看滚动事件。看下面的例子:html5

Scroll events counterjava

当咱们使用触控板,滚轮或者用鼠标拖动滚动条的时候会轻易地每秒触发30次事件。可是在智能手机上缓慢地滚动页面会很容易的触发100次事件每秒。请问你的事件处理器准备好了这样的触发频率吗?node

在2011年,推特网上出现了这样一个问题:当你向下滚动你的简讯页面的时候,页面变得缓慢并且没法响应。John Resig发布了一篇文章a blog post about the problem来描述将作了不少复杂操做的函数绑定到scroll事件上是一个多么坏的主意。jquery

John建议的解决办法(在那个时候,五年前)是在scroll事件以外循环运行函数,每过250ms执行一次。这样的话事件处理函数就不会被同时触发运行。经过这个简单的方法,就能够避免用户执行贵重的操做。webpack

这些天来处理事件的复杂精妙的方法变得更多了。让我来向你介绍去抖,节流和requestAnimationFrame。咱们还会看与它们相匹配的使用场景。git

Debounce

去抖技术容许咱们将屡次相继触发的函数调用集合成单一的一次调用。

想象你在一个电梯中。电梯门开始闭合,而后忽然间另一我的想要进入电梯。电梯不会开始执行改变楼层的函数,而是从新打开电梯门。如今另外一我的出现了又会从新打开门。电梯虽然会延迟它的函数(变换楼层)执行,可是这样优化了资源。

本身试一试。在按钮上方点击或者移动指针。

Debounce. Trailing

从上面的例子能够看到一个单个去抖事件下相继快速发生的事件触发是如何表现的。可是若是事件触发之间有很大的时间间隔,去抖就不会发生。

Leading edge (or "immediate")前边缘(或者说当即)

你也许会以为恼怒,由于去抖的事件会在触发函数执行以前一直等待,直到事件中止的时候忽然触发函数。为何不当即触发函数执行,这样就会表现地像没有去抖的原始事件处理函数同样?可是不须要在忽然执行函数的停顿的时候再次触发函数。

你能够这样作!下面是在等待以前就触发的例子:

在underscore库中,这个配置项叫immediate而不是leading

下面的例子本身试一试:

Debounce. Leading

去抖函数的实现

我第一次看到去抖函数的javascript实现是在2009年在John Hann的这篇博客文章(也是他发明了这个术语)。

以后,Ben Alman创造了一个jquery插件(已经再也不维护),一年以后,Jeremy Ashkenas将去抖函数加入到了underscore.js中。最后lodash中也加入了去抖函数。

三种实现的内部代码都有一点区别,可是表面上使用起来都是一致的。

曾有一段时间underscore采用了lodash的去抖和节流实现,在2013年我在_.debounce方法之中发现了一个bug以后。自从那次以后,两个库的实现都变得不同了。

lodash库为_.debounce和_.throttle函数添加了更多的特性。原来的immediate标识被替换成了leading和trailing选项。你能够选择其中之一或者两个都选。默认状况下只有trailing选项是开启的。

新的maxWait选项(只有lodash库有这个参数)不在本文讨论范围内可是这个参数仍是颇有用的。实际上,节流函数的定义就是_.debounce和maxWait组合的结果,你能够去看看lodash源码。

去抖函数例子

resize

当改变桌面浏览器窗口的大小的时候,浏览器会触发不少次resize事件当用户拖动窗口大小的时候。

看看下面这个demo:

Debounce Resize Event Example

正如你所见,咱们对于resize事件使用默认的trailing配置,由于咱们只对于最后的窗口大小值感兴趣,当用户中止改变窗口大小的时候。

鼠标按下事件自动完成表单而后发送ajax请求

当用户仍然在输入的时候,为何每过50毫秒就要发送请求到服务器?_.debounce能够帮助咱们避免额外的工做,只有在用户中止打字的时候才发送请求。

在这里使用leading标识是没有意义的,咱们想要等待到用户输入最后一个字母的时候。

Debouncing keystrokes Example

一个相似的使用场景就是等待直到用户中止输入而后验证用户的输入,而后提示“密码过短”之类的提示信息。

去抖和节流如何使用?以及容易犯的错误

建立你本身实现的去抖和节流函数是颇有吸引力的,或者从网上找一些别人的博文里的实现。个人建议是直接使用underscore和lodash库。若是你只须要库中的_.debounce和_.throttle函数,你可使用lodash自定义构建器来输出一个自定义的缩小的库。使用下面的命令来建立:

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

也就是说,最多见的状况就是使用lodash的throttle和debounce模块或者直接引入单独的模块到webpack,browserify,rollup中打包。

一个容易犯的错误就是调用_.debounce函数超过一次:

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

// RIGHT
$(window).on('scroll', _.debounce(doSomething, 200));

为去抖函数建立一个变量能够容许咱们调用去抖的私有方法debounced_version.cancel(),lodash和underscore均可用,也许你会须要这样的功能。

var debounced_version = _.debounce(doSomething, 200);
$(window).on('scroll', debounced_version);

// If you need it
debounced_version.cancel();

Throttle

经过使用_.throttle,咱们不容许函数在单位时间X毫秒内执行超过一次。

节流和去抖的主要区别就是节流函数会保证了函数的执行是有规律的,至少每X毫秒只能执行一次。

和去抖函数同样,节流技术可使用Ben的插件,或者underscore或者lodash。

节流函数例子

无限滚动

这是一个比较广泛的例子。用户向下滚动无限滚动的页面。你须要判断当前滚动的位置和页面底部的距离。若是用户的位置靠近页面底部了,咱们应该凭借ajax请求更多的页面内容而后将内容插入页面结尾。

这里_.bounce函数就不能帮上忙了。由于它只能在用户中止滚动的时候触发请求。而咱们须要在用户尚未抵达页面底部的时候就开始获取新的内容。

经过_.throttle咱们能够保证持续检查当前的位置距离页面底部的距离。

Infinite scrolling throttled

requestAnimationFrame (rAF)

requestAnimationFrame是另一种方式来限制函数调用的频率。

它能够被认为是_.throttle(dosomething, 16)。可是它拥有更高的保真度,由于它是浏览器原生API以更好的精确性为目标。

咱们可使用rAF API做为一个可选方案来使函数节流,思考下面举出的优缺点:

优势

  • 目标为60fps(每16ms一帧)可是内部程序会决定最佳时间如何去安排页面渲染
  • 简单并且标准的API,将来不会改变。更少的兼容性问题

缺点

  • 何时启动和结束rAF方法是咱们的责任,而不像debounce或者throttle方法那样在内部被管理。
  • 若是浏览器tab页没有被激活,那么它就不会执行。虽然对于滚动,鼠标和键盘事件来讲这一点无所谓。
  • 虽然全部现代浏览器支持rAF方法,可是IE9,Opera Mini还有旧的安卓手机依然不支持。现在仍然须要一个polyfill。
  • nodejs中不支持rAF,因此你没法在服务端使用它来对文件系统的事件函数节流。

单凭经验来讲,我会使用requestAnimationFrame方法若是你的js函数是做为绘制页面元素或者动画的用途,若是牵扯了从新计算元素位置那么就应该使用它来控制。

当发送ajax请求,或者决定是否添加或移除一个css class(这样会触发一个css动画),我会考虑使用去抖或者节流方法,这样你能够设置更低的执行比率(200ms执行一次而不是16ms)。

若是你认为rAF方法应该在underscore或lodash库中被实现,可是这两个库都拒绝实现rAF,由于它属于一种特殊使用场景,并且原生的API已经足够简单来直接调用。

rAF例子

我只会在这个例子上演示rAF方法如何控制滚动事件的动画,受到Paul Lewis的文章的启发,他在文章中一步一步解释了这个例子的逻辑。

我将rAF控制的动画和_.throttle节流函数(每16毫秒)控制的动画并排放在一块儿来直观比较。最后两个函数的表现差很少同样,可是也许rAF方法会在更加复杂的场景中有更好的表现。

Scroll comparison requestAnimationFrame vs throttle

关于rAF技术的更加高级的例子我曾在headroom.js这个库中看到过,其实现的逻辑减弱了并且将操做方法包裹在一个对象中。

Conclusion

使用去抖,节流或者requestAnimationFrame去优化你的事件处理函数。每一种技术都有一点差别,可是这三种方法都颇有用而且它们互相补充。

总结:

去抖函数:将忽然连续触发的事件(例如频繁按下键盘按键)集合成单独的一次触发。

节流函数:保证每单位时间X毫秒内函数的执行是流畅的。例如每200毫秒就检查一次滚动位置来触发css动画。

requestAnimationFrame函数:一个可选的节流方法。当你的函数在页面上从新计算和渲染元素的时候,或者你想要保证平滑的动画,那么就用它。注意:IE9浏览器不支持这个方法。

相关文章
相关标签/搜索