通过例子解释防抖和节流

这是一篇译文,是一篇我感觉对防抖和节流解释比较好懂的博客。原文在这里

防抖和节流是两个相似的技术去控制很多次我们允许一个函数去执行在一定时间内

有防抖和节流的函数是特别有用的,当我们把这个函数绑到DOM事件时。为什么?因为我们有了一个控制层在事件和执行函数之间。记住,如果我们不去控制DOM事件的冒泡,它的数量可以非常巨大。

例如,让我们看看滚动事件,看下面的例子:

当我们使用触摸板,滚轮或者仅仅是拖动滚动条时每秒就能触发30次滚动事件。而且在手机上缓慢的滚动每秒能触发100次滚动事件在我的测试中。你的滚动函数准备好这种速度的执行了吗?

在2011年,一个问题在Twitter网站上出现:当你向下滚动你的Twitter feed时,它会变慢而且没有响应。John Resig发表的一篇博客提出了这个问题并解释直接把耗时操作绑到scroll事件上是一个坏主意。

John建议的解决方法是每250ms跑一个循环,在onScroll事件之外。这种方法处理函数没有与事件耦合在一起。使用这种简单的技术,我们避免了毁灭性的用户体验。

如今事件处理方法会更加复杂。让我向你介绍防抖,节流和rAF。当然我们会使用适合的例子。

防抖

防抖技术允许我们将多个分散的调用“合并”成一个。

在这里插入图片描述

想像一下你在一个电梯上,电梯门就要关闭了,突然另外一个人想要上电梯,所以在门外按了按钮。电梯并不会开始改变楼层,而是重新打开门。下一个人也是一样。电梯会延时它的行为(移动楼层)从而优化资源使用。

试一下下面的例子,在顶部按钮点击或者移动鼠标:

在这里插入图片描述

你能发现多个顺序快速触发的事件被一个简单的防抖事件取代。但是被触发的事件的是一个大间隔,防抖则不会发生。

前沿触发(立即触发)

你一定发现了防抖事件需要等到事件发生得不那么快速才能触发执行。为什么不立即触发防抖函数执行呢?这样它的行为更接近原始没有防抖时的函数,而且不会继续调触发当调用速度很快的时候。

当然是可以的,这里有一个立即触发例子:

在这里插入图片描述

在underscore.js里面立即触发的选项叫immediate。

自己试试吧:

在这里插入图片描述

实现防抖

第一次我看见防抖的JavaScript实现是在John Hann在2009年的一篇博客上。(他创造了这个词)

之后,Ben Alman写了一个jQuery插件。然后一年后,Jeremy Ashkenas把它添加到了underscore.js里面。最后它被添加到了Lodash(一个underscore.js的替代方案)里面。

三种实现内部实现可能有一点不同,但是它们的接口是相同的。

就在那个时候underscore采纳了Lodash的防抖和节流的实现,在2013年我发现了_.debounce函数的一个BUG之后。它们就分开实现了。

Lodash在它的_.debounce和_.throttle函数里面添加了更多的特性。原始的immediate标志被leading和trailing标志代替。你可以选择一个,也可以两个都选。默认只有trailing标志是真。

新的maxWait选项(目前仅仅在Lodash里)不是本文叙述之内,但是它是真的很有用。事实上,在Lodash源码里面节流函数就是使用防抖函数加maxWait选项定义的。

防抖例子

调整页面大小例子

当一个浏览器窗口大小发生变化时,可能触发很多次resize事件

就像下面的例子:

在这里插入图片描述
如你所见,我们使用默认的trailing选项的防抖函数在resize事件中,因为我们只对用户停止调整大小的最后一个值感兴趣。

输入在自动提交表单中带Ajax请求

为什么当用户持续输入的时候,要每隔50ms向服务器发送Ajax请求呢?_.debounce可以帮我们避免多余的工作,而且只有当用户停止输入时发送请求。

在这个例子里leading选项是没有意义的,我们需要 等用户最后一个字母输入完。

在这里插入图片描述

一个相似的例子是我们需要等到用户停止输入才能校验他输入的值并输出“你的密码太短”等类型的信息。

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

编写自己的防抖/节流函数或者从其它的博客里面复制函数是充满诱惑的。但是我的建议是直接使用underscore或者Lodash库。如果你需要_.debounce或者_.throttle函数,使用Lodash命令可以生成一个只有2KB的压缩过的库。只需要使用下面简单的命令:

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

再说了,也可以使用webpack/browserify/rollup工导入lodash/throttlelodash/debounce模块或者lodash.debouncelodash.throttle模块。

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

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

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

将防抖函数赋值给一个变量可以允许我们调用私有的方法debounced_version.cancel(),可以在underscore和Lodash框架使用。下面是例子。

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。

节流例子

无限滚动
这是一个常见的例子。用户在一个无限长的页面上划动。你需要知道用户距离底部还有多远。如果用户接近底部,我们就需要请求更多内容添加到页面底部。

这里我们心爱的_.debounce就没有用了。它只能当用户停止滚动的时候触发,而我们需要请求数据当用户到达底部之前。

在这里插入图片描述

而使用_.throttle我们则能保证不断地检查我们距离底部有多远。

requestAnimationFrame(rAF)

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

它可以理解成_.throttle(dosomething, 16)。但是更准确的说,它是更精确的浏览器原生API。

我们可以使用rAF API,作为节流函数的代替。但是必须考虑优缺点。

优点

  • 目标是60fps(每帧16毫秒)但是内部会决定渲染周期最好的时机
  • 更简单和标准的API,未来极少变动。不需要维护。

缺点

  • rAF的开始和取消需要手动,不像_.debounce_.throttle已经集成在内部。
  • 如果浏览器标签不是打开的,它可能不会执行。尽管对于滚动,鼠标和键盘事件不用担心。
  • 尽管现代浏览器都支持了rAF,但是IE9,Opera Mini和早期的Android不支持。可能还是需要一个polyfill
  • rAF不支持node.js,所以你不能在服务器上节流文件系统的事件。

根据经验,如果你的JavaScript函数渲染元素或者有动画使用requestAnimationFrame函数比较好。

发送Ajax请求或者决定是否添加或删除一个样式(触发CSS动画)我会选择_.debounce或者_.throttle来降低函数执行的速度(例如:200ms,代替16ms)。

如果你认为rAF被内置到underscore或者Lodash里面,它们都拒绝了这个想法,因为这是一个专门的例子,它很容易使用。

rAF例子

这个例子在滚动事件上使用rAF,灵感来自Paul Lewis的一篇一步一步解释这个例子逻辑的文章。

我把它和配置了16ms的_.throttle方法放在一起。它们的行为非常相似,但是rAF在更复杂的场景展现会更好。

在这里插入图片描述

我见过使用这种技术的一个更高级的例子是在headroom.js里面将逻辑解耦并包装在一个对象的时候。

总结

使用防抖,节流和requestAnimationFrame来优化你的事件调用函数吧,每种技术都不同,但又互为补充。

总结:

  • 防抖:合并一些密集快速的事件为单一的一次。
  • 节流:保证每隔X毫秒会执行一次。比如每隔200ms检查一下你滚动的位置来触发CSS动画。
  • requestAnimationFrame:节流 的代替。当你想让你的函数重新计算或渲染元素时保证流畅的变化时用它。注意:不支持IE9。

猜你喜欢

转载自blog.csdn.net/cuipp0509/article/details/117256073