-
打个分吧:

动画demo解释 防抖、节流、rAF(CSS Tricks译文)

这三种技术用于优化事件处理函数,都很有用,且各有区别,互相补充。

6分钟阅读
-
-

原文链接:防抖与节流的区别

译者注:为了文章更易理解,对原文略有改动


防抖节流是两种相似(但不同)的技术,用于控制一定时间内函数的执行次数。

当我们为DOM事件添加事件处理函数时,防抖或节流函数十分有用。为什么呢?因为我们并不能控制DOM事件被触发的频率,而防抖和节流在事件和事件处理函数之间,为我们添加了一个控制层。

例如scroll事件,看这个demo:

当使用触控板,滚轮,或者滚动条滚动时,每秒钟可以轻易地触发30个事件。但是在我的测试中,在手机中缓慢滑动屏幕,每秒可以触发多达100个事件。你能确保你的事件处理函数在这种执行频率下正常工作吗?

2011年,Twitter网站上出现了一个问题:当你在向下滚动Twitter feed时,网页变得缓慢且无响应。John Resig发表了一篇关于这个问题的博客,文章中解释了将消耗昂贵的函数直接附加到scroll事件上是多么的糟糕。

John建议的解决方案(当时是五年前)是在onScroll事件之外,每隔250ms运行一个循环。这样处理程序就不会与事件耦合。通过这个简单的技术,我们可以避免破坏用户体验。

现如今,有一些更复杂一些的处理事件的方法。让我给大家介绍一下DebounceThrottlerequestAnimationFrame,以及相应的demo。

Debounce 防抖

Debounce 防抖技术允许我们将多次连续的执行"分组"到一次单一的执行中。

https://i0.wp.com/css-tricks.com/wp-content/uploads/2016/04/debounce.png

想象一下这样的场景,你在电梯里,电梯门开始关闭,突然有另一个人想上电梯,电梯则不会运行,门会再次打开。然后又有一个人要上电梯,电梯再次延迟了它的运行(移动楼层),但优化了它的资源。

可以在下面的示例中,尝试在顶部的“Trigger area”中点击或移动:

在上面的示例中可以看到,debounced事件是如何代替一组连续快速触发事件的。但如果事件的触发有很大的时间间隔,则不会发生debouncing。(可以这样理解,如果一直有人要上电梯,电梯就不会运行,直到等待一定时间内无人上电梯,电梯就会开始运行,在上述的示例中,这个等待时间被设定为四个刻度,也就是400ms)

leading / immediate 参数

在上述的示例中,debouncing事件需要等待,直到事件在一定时间内停止触发,才会执行函数。这种场景与等电梯的场景吻合。

如果有另一种场景,需要在事件触发时,就立即执行函数,在快速连续触发的过程中,直到有一个暂停(满足等待时间),才会再次执行函数。

这种需求,可以通过leading参数来实现:(在underscore.js中,这个参数的名称叫 immediate )

https://i2.wp.com/css-tricks.com/wp-content/uploads/2016/04/debounce-leading.png

leading”防抖的demo:

Debounce 实现

我第一次看到debounce的Javascript实现是2009年,在John Hann的这篇文章中(他也是这个词的发明者)。

不久之后,Ben Alman创建了一个jQuery插件(不再维护),一年之后,Jeremy Ashkenas将其添加到了underscore.js中。后来,它又被添加到Lodash中,成为undererscore的替代方案。

这3种实现内部有些不同,但它们的接口都差不多。

曾经有一段时间,在我于2013年发现_.debounce函数中的一个bug之后,underscore采用了Lodash的debounce / throttle实现。从那时起,两种实现都有了长足的发展。

Lodash在其_.debounce_.throttle函数中 增加了 更多的功能。原来的immediate 标志被替换为leading 和trailing 选项。你可以选择启用一个,或者两个。默认情况下,只有trailing 被启用。(leading可以理解为,在一组连续触发事件的起始,就调用函数;而trailing,则是在一组连续触发事件的末尾,经过等待时间后,执行函数)

还有一个新的maxWait选项(目前只在Lodash中使用)在本文中没有涉及,但它可能非常有用。

实际上,在Lodash的源码种,throttle节流函数是用通过_.debouncemaxWait选项来定义的。

Debounce 实例

Resize 实例

当调整(桌面端)浏览器窗口的大小时,可能会触发许多的resize事件。

可以在下面的demo中看到:

如你所见,上面的例子中,启用了默认的trailing选项,因为我们只关心用户停止resize后的最终值。

带有AJAX请求的自动填充输入框的输入事件

有一些场景例如等待用户停止输入后再验证其输入,反馈验证信息。这种场景下 _.debounce可以实现:只有当用户停止输入时才发送请求。

此时,leading 标志没有意义,因为我们需要等待至最后的输入。

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

你可以自己写debounce/throttle函数,或者从一些随机的博客文章中复制它,但我的建议是直接使用 underscoreLodash库。

如果你只需要_.debounce_.throttle函数,你可以使用Lodash自定义构建器来输出一个自定义的2KB minified库。下面时构建命令:

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

也就是说,大多数人都是通过webpack/browserify/rollup工具,使用模块化形式的lodash/throttlelodash/debouncelodash.throttlelodash.debounce包。

一个常见的陷阱是,多次调用_.debounce 函数:

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

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

在lodash 和 underscore.js中,为debounced饭都处理过的函数创建一个变量,可以调用私有方法 debounced_version.cancel()

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

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

Throttle 节流

通过使用 _.throttle, 可以限制函数在 X 毫秒内,最多只能执行一次。

debouncing的主要区别在于,节流保证了函数的定期执行,至少每X毫秒一次。

Throttling 实例

无限滚动

举一个很常见的例子,用户正在向下滚动你的无限滚动页面。你需要检查用户离底部有多远。如果用户在底部附近,我们应该通过Ajax请求更多的内容,并将其添加到页面中。

在这种场景下,_.debounce就不适用了,它只有在用户停止滚动时才会触发......而我们需要在用户到达底部之前开始获取内容。而_.throttle可以保证我们不断地检查用户离底部有多远。

requestAnimationFrame (rAF)

requestAnimationFrame 是限制函数执行速度的另一种方式。

它相当于_.throttle(dosomething, 16),但保真度要高很多,因为它是浏览器原生的API,拥有更好的准确性。

可以考虑使用rAF API,作为节流函数的替代品,以下是它的优缺点:

优点:

  • 目标为60fps(即每秒60帧),但是由浏览器内部机制决定如何安排渲染的最佳时间。
  • 更简单和更标准的API,未来不会改变,更好维护。

缺点:

  • rAFs的启用/取消是我们的责任,不像debouncethrottle,是内部管理的。
  • 如果浏览器标签页未激活,它就不会被执行。(对于滚动、鼠标或键盘事件来说,这并不重要)。
  • 虽然所有的现代浏览器都提供了RAF,但在IE9、Opera Mini和旧的Android中仍然不支持。仍然需要polyfill 。

根据经验来讲,如果我的JavaScript函数是 "绘画 ",或者会直接变更动画相关属性,我会使用requestAnimationFrame,以及在一切涉及重新计算元素位置的地方使用它。

如果要进行Ajax请求,或者决定是否添加/删除一个class(可能会触发CSS动画),我会考虑_.debounce_.throttle,因为可以设置更低的执行速率(例如200ms,而不是16ms)。

rAF 实例

这个demo灵感来自于 Paul Lewis的文章, 文章做他详细解释了demo中的原理和逻辑。

我把rAF和  16ms 的_.throttle 放在一起进行了比较,结果是它们的性能相似。但是在更复杂的情况下,rAF可能性能会更高。

我在headroom.js库里见到过rAF技术更高级的实例,其中的 逻辑被解耦 并且被包装在了对象中。

总结

debounce防抖, throttle 节流和 requestAnimationFrame 可以用来优化事件处理函数,三种技术都很有用,且各有区别,互相补充:

  • debounce防抖: 将快速连续的多次事件触发分组,归为一次执行。
  • throttle节流: 确保每隔X 毫秒就有一次稳定的执行,例如每200ms检查一次用户滚动位置以触发一个CSS动画。
  • requestAnimationFrame: 节流函数的16ms替代选择。更适用于在页面上重新计算/渲染元素的函数,能得到更平滑的动画。但是注意: IE9 不支持。
上次更新:

评论区