通过示例解释消抖和节流

大卫·科尔巴科的阿凡达
大卫·科尔巴科

DigitalOcean为您旅程的每个阶段提供云产品。开始使用200美元的免费信贷!

以下是一篇来宾帖子大卫·科尔巴科他是伦敦的前端工程师。我们已经以前提出过这个话题但这次,David将通过交互式演示将概念带回家,让事情变得非常清楚。

辩论节气门是两种类似(但不同)的技术,用于控制一个函数在一段时间内的执行次数。

当我们将函数附加到DOM事件时,函数的去阻塞或节流版本特别有用。为什么?因为我们在事件和函数执行之间给了自己一层控制。记住,我们不控制这些DOM事件的发出频率。它可以变化。

例如,让我们谈谈滚动事件。请参见此示例:

查看钢笔滚动事件计数器科尔巴科(@dcorb(dcorb))上的代码笔.

当使用轨迹板、滚轮或仅仅拖动滚动条滚动时,每秒可以轻松触发30个事件。但在我的测试中,在智能手机中缓慢滚动(交换)可能每秒触发多达100个事件。您的滚动处理程序是否为这种执行速度做好了准备?

2011年,推特网站上突然出现了一个问题:当你向下滚动推特提要时,提要变得缓慢而无响应。John Resig出版关于这个问题的博客文章其中解释了将昂贵的函数直接附加到纸卷事件。

John(五年前的那个时候)建议的解决方案是在onScroll事件。这样,处理程序就不会与事件耦合。使用这个简单的技术,我们可以避免破坏用户体验。

如今,处理事件的方式稍微复杂一些。让我向您介绍Deboush、Throttle和requestAnimationFrame。我们还将查看匹配的用例。

辩论

Deboush技术允许我们在单个调用中“分组”多个连续调用。

想象一下你在电梯里。门开始关闭,突然另一个人试图上车。电梯没有开始改变楼层的功能,门又打开了。现在这又发生在另一个人身上。电梯延迟了其功能(移动楼层),但优化了其资源。

自己试试吧。单击或移动按钮顶部的鼠标:

查看钢笔辩论。尾部科尔巴科(@dcorb(dcorb))上的代码笔.

您可以看到序列快速事件是如何由单个去抖事件表示的。但是,如果这些事件是由巨大的差距引发的,那么就不会出现反弹。

前缘(或“立即”)

你可能会发现脱抖事件令人恼火等待在触发函数执行之前,直到事件停止如此迅速地发生。为什么不立即触发函数执行,使其行为与原始的非debounded处理程序完全相同?但在快速呼叫暂停之前,不要再次开火。

你可以做到!下面是一个带有主要的标志打开:

“领先”去抖动示例。

在underscore.js中,该选项称为立即的而不是主要的

亲自尝试:

查看钢笔辩论。领先科尔巴科(@dcorb(dcorb))上的代码笔.

取消公告实施

我第一次看到JavaScript中实现deboush是在2009年这个约翰·汉恩的帖子(他也创造了这个词)。

不久之后,本·阿尔曼创建了jQuery插件(不再保留),一年后,杰里米·阿什克纳斯(Jeremy Ashkenas)将其添加到underscore.js它后来被添加到Lodash中,作为下划线的替代品。

这三种实现在内部略有不同,但它们的接口几乎相同。

有一段时间,在我发现了一个错误在中_.去抖动2013年的功能。从那以后,这两种实现都变得不同了。

洛达什已添加更多功能_.去抖动_.节气门功能。原件立即的标志替换为主要的拖尾选项。你可以选择一个或两个。默认情况下,只有拖尾边缘已启用。

新的最大等待时间选项(目前仅在Lodash中)未在本文中介绍,但它可能非常有用。实际上,油门功能是使用_.去抖动具有最大等待时间,正如你在灰烬中看到的那样源代码.

辩论示例

调整大小示例

当调整(桌面)浏览器窗口的大小时,它们可以发出许多调整大小事件,同时拖动调整大小句柄。

在本演示中亲自观看:

查看钢笔Deboush Resize事件示例科尔巴科(@dcorb(dcorb))上的代码笔.

如您所见,我们使用默认值拖尾选项,因为我们只对用户停止调整浏览器大小后的最终值感兴趣。

按键使用Ajax请求自动完成表单

当用户还在打字时,为什么每隔50毫秒向服务器发送一次Ajax请求?_.去抖动可以帮助我们,避免额外的工作,并且只在用户停止键入时发送请求。

在这里,拥有主要的打旗子。我们想等最后一封信打出来。

查看钢笔消除击键示例科尔巴科(@dcorb(dcorb))上的代码笔.

类似的用例是等待用户停止输入,然后再验证其输入。“您的密码太短”类型的消息。

如何使用去抖动和节流以及常见陷阱

建立你自己的去抖动/节流功能,或者从一些随机的博客帖子中复制它,都很诱人。我的建议是直接使用下划线或Lodash。如果您只需要_.去抖动_.节气门函数,您可以使用Lodash自定义生成器输出自定义2KB缩小库。使用以下简单命令构建它:

npm i-g lodash-clilodash include=去抖,节流

也就是说,大多数使用模块化形式的“lodash/throttle”和“lodash/deboush”或“lodashi.throttles”以及带有webpack/browserify/rollup的“lodiash.deboush“包。

一个常见的陷阱是调用_.去抖动功能多次:

//错误$(window).on('scroll',function(){_.deboush(doSomething,300);});//权利$(window).on('scroll',_.deboush(doSomething,200));

为debounded函数创建一个变量将允许我们调用private方法debound_version.cancel(),有lodash和undercore.js两种版本可供选择,以备不时之需。

var debound_version=_.deboush(doSomething,200);$(window).on('scroll',debound_version);//如果你需要的话debound_version.cancel();

节气门

通过使用_.节气门,我们不允许函数每X毫秒执行一次以上。

这与去抖动之间的主要区别在于,节流阀保证了函数的定期执行,至少每X毫秒执行一次。

与去抖动一样,本的插件underscore.js和lodash也涵盖了节流技术。

节流示例

无限滚动

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

这里是我们的爱人_.去抖动不会有帮助的。只有当用户停止滚动时才会触发。。我们需要开始获取内容之前用户到达底部。
使用_.节气门我们可以保证,我们会不断检查我们离底部有多远。

查看钢笔限制无限滚动科尔巴科(@dcorb(dcorb))上的代码笔.

请求动画帧(rAF)

请求动画帧是另一种降低函数执行速度的方法。

它可以被认为是_.节气门(dosomething,16)但它的保真度要高得多,因为它是一种旨在提高准确性的浏览器本机API。

考虑到这一利弊,我们可以使用rAF API作为油门功能的替代:

赞成的意见

  • 目标是60帧/秒(16毫秒的帧),但内部将决定如何安排渲染的最佳时间。
  • 相当简单和标准的API,未来不会改变。维护更少。

欺骗

  • rAF的启动/取消是我们的责任,不同于.去跳动.节气门由内部管理。
  • 如果浏览器选项卡未激活,则不会执行。尽管对于滚动、鼠标或键盘事件,这并不重要。
  • 尽管所有现代浏览器都提供rAF,但IE9、Opera Mini和旧Android仍不支持。聚合填料需要直到今天。
  • node.js不支持rAF,因此不能在服务器上使用它来限制文件系统事件。

作为经验法则,我会使用请求动画帧如果JavaScript函数是“绘制”或直接设置属性动画,请在涉及重新计算元素位置的任何地方使用它。

要发出Ajax请求,或决定是否添加/删除类(这可能会触发CSS动画),我会考虑_.去抖动_.节气门,可以设置较低的执行速率(例如200ms,而不是16ms)

如果您认为rAF可以在下划线或lodash中实现,他们都拒绝了这个想法,因为这是一个专门的用例,而且很容易直接调用。

rAF示例

我将只介绍这个示例,以使用滚动条上的requestAnimation帧,其灵感来自保罗·刘易斯文章,他逐步解释了这个例子的逻辑。

我把它放在一起比较_.节气门16毫秒。提供类似的性能,但rAF可能会在更复杂的场景中提供更好的结果。

查看钢笔滚动比较请求AnimationFrame与油门科尔巴科(@dcorb(dcorb))上的代码笔.

我看到的一个更高级的示例是库headroom.js,其中逻辑解耦并包裹在一个对象中。

结论

使用去抖、节流和请求动画帧以优化事件处理程序。每种技术都略有不同,但这三种技术都是有用的,并且相互补充。

总之:

  • 去抖动:将突发事件(如击键)分组为单个事件。
  • 节气门:保证每X毫秒执行一次的恒定流。就像每隔200毫秒检查一次滚动位置以触发CSS动画一样。
  • 请求动画帧:油门替代方案。当您的函数重新计算并在屏幕上呈现元素时,您需要保证平滑的更改或动画。注意:不支持IE9。