这篇文章讲概念、应用场景和实现思路,源码在最后面。
性能和速度是程序的敌人,以致于每一个优秀的程序员都在孜孜不倦的提升软件的性能和速度,从而提升产品的用户体验。
下面介绍的是debounce和throttle,这两种技术能够改善程序的性能,它们非常相似但是不同的技术。
当dom事件被频繁触发时,使用debounce或throttle就非常有用了,因为它能够在事件和函数执行之间添加一层控制。
这里推荐使用Lodash
工具库,引入后直接使用_.debounce
和 _.throttle
,非常简单和方便,当然前提是需要理解它们的作用。
debounce防抖动
debounce允许我们把一组调用压缩为单个调用。
想象一下你在电梯里面,门准备关上,突然,有人闯进来,这时电梯不会开始移动,而是门重新打开,然后等待一会。如果还有人进来,它会重复这一过程,直到没有人再进来,最后门自动关上,电梯开始在楼层间移动。这是为什么?因为工程师们也对电梯进行了资源优化,用更少的资源完成任务,充分发挥电梯的效用。这个电梯的例子跟debounce的应用场景非常相似。
应用场景:
- 调整resize。
当监听了窗口的resize事件时,并且调整浏览器窗口大小的时候,会触发大量的resize
事件,所以呢当你拖拽的时候,调用处理函数做大量计算的时候,会发现拖拽的过程有点卡顿,掉帧。
这时我们可以使用debounce,因为我们关注的是最终值,也就是我们最后停止拖动浏览器窗口的值。
- input的输入和发送请求。
在input里输入文字然后发送网络请求,有一种较优的方案就是期望用户输入完毕然后再发送网络请求,而不是输入的过程中不断的发出请求,这能有效的优化网络服务。
throttle节流
throttle 只允许在每x毫秒内执行一次操作。
它跟debounce的不同主要是:throttle的执行是有规律的,会每x毫秒内执行一次。
使用场景:
一个相当普遍的例子,用户在使用一个无限滚动的页面,你需要检测用户的位置到底部之间的距离,如果用户接近屏幕底部,我们应该发送Ajax请求更多内容然后添加到页面上。实现这一功能需要监听页面的scroll事件,然而在手机端缓慢的滚动页面会触发上百次事件,这时候有了throttle就可以对其进行优化,比如每250ms内只调用一次,这样用户基本感觉不到有任何体验上的差别,也优化了程序的性能。
在这里我们心爱的debounce是不适合的,因为debounce只在用户停止滚动时才触发(调用)函数。而我们需要的是在用户在滚动页面过程中快要到达底部的时候获取更多内容,使用throttle才能持续的进行检测用户位置到底部之间的距离。
requestAnimationFrame (rAF)
rAF 是对函数的执行进行限速的额外的一种方式。大致等同于_.throttle(dosomething, 16)
。但它可以是throttle的替代者,它更流畅和平滑,它是浏览器的标准API。
根据经验,我使用rAF函数的一些场景主要是绘画或动画,重新计算元素的位置等。
触发网络请求或决定是否添加/移除一个class(触发一个CSS动画),我会考虑_.debounce or _.throttle,使用它们你能更灵活的控制执行速率(用200ms替换16ms)。
在underscore或lodash这两个框架里面都没有提供或实现rAF,因为使用它非常的简单。
例子:
var start = null;
var element = document.getElementById('SomeElementYouWantToAnimate');
element.style.position = 'absolute';
function step(timestamp) {
if (!start) start = timestamp;
var progress = timestamp - start;
element.style.left = Math.min(progress / 10, 200) + 'px';
if (progress < 2000) {
window.requestAnimationFrame(step);
}
}
window.requestAnimationFrame(step);
总结:
使用debounce, throttle and requestAnimationFrame来优化你的事件操作。三者的技术和思路都略微不同,但它们都是非常有用以及彼此互补。
- debounce:debounce允许我们把一组事件调用压缩为单个调用。
- throttle:每x毫秒执行一次。就像每隔200毫秒检查一次滚动位置。
- requestAnimationFrame:可以是throttle的替代者,当你的函数为重新计算和渲染元素到屏幕上,而且你想得到平滑的过渡动画。注意:不支持IE9。
Github:
throttle 和 debounce函数代码
实现思路:
- debounce:创建一个指定
x
毫秒的setTimeout(fn,x)
,如果在该setTimeout(fn,x)
的x毫秒内再次产生新的事件,就会先去取消该setTimeout
,然后重新创建一个指定x
毫秒的setTimeout(fn,x)
,不断重复这一过程,直到没有再产生新的事件,x
毫秒后调用函数fn
。 - throttle:
setTimeout(fn,x - elapsed)
。节流的实现要多两个额外的变量,一个是记录上一次函数fn
执行的时间(lastExec
),一个是记录流逝的时间(elapsed) = 当前setTimeout创建的时间 - 上一次函数fn执行的时间(lastExec)
,每产生一个新的事件(创建新的setTimeout
),都会先去取消上一个setTimeout
,所以它的定时器要这么写setTimeout(fn,x - elapsed)
就能每x毫秒执行一次函数fn
了,每一次函数fn被执行都会更新lastExec的值为当前时间。
参考资料:
https://css-tricks.com/debouncing-throttling-explained-examples/