在开发过程中会遇到频率很高的事件或者连续的事件,如果不进行性能的优化,就可能会出现页面卡顿的现象,比如:
为了解决这类问题,常常使用的方法就是throttle(节流)和debounce(去抖)。throttle(节流)和debounce(去抖)都是用来控制某个函数在一定时间内执行多少次的解决方案,两者相似而又不同。
我们要知道一点,浏览器事件的callback会block浏览器自身的行为(例如上面提到的resize、scroll...), 也就是比如一个scroll触发的callback要执行一秒,那么浏览器会等callback执行的一秒结束后,才会开始执行自己的scroll行为。这样的话如果每滑动一次就执行一次, 这个页面就废了。 所以要尽量保证callback能立即执行完(即使有更大的运算也要扔到另一个function作为callback)
早在2011年,Twitter 网站抛出了一个问题:向下滚动 Twitter 信息流的时候,变得很慢,很迟钝。John Resig 发表了一篇博客解释这个问题,文中解释到直接给 scroll 事件关联昂贵的函数,是多么糟糕的主意。
John(5年前)建议的解决方案是,在 onScroll 事件外部,每 250ms 循环执行一次。简单的技巧,避免了影响用户体验。现如今,有一些稍微高端的方式处理事件。我来结合用例介绍下 Debounce,Throttle 和 requestAnimationFrame 吧。我首次看到 debounce 的 JavaScript 实现是在 2009 年的 John Hann 的博文。不久后,Ben Alman 做了个 jQuery 插件(不再维护),一年后 Jeremy Ashkenas 把它加入了 underscore.js。而后加入了 Lodash 。
一、throttle和debounce区别:
throttle和debounce的作用就是确认事件执行的方式和时机,以前总是不太清楚两者的区别,容易把二者弄混。下面就通过两个简单的场景描述一下debounce和throttle,以后想到这两个场景就不会再弄混了:
debounce
假设你正在乘电梯上楼,当电梯门关闭之前发现有人也要乘电梯,礼貌起见,你会按下开门开关,然后等他进电梯;
如果在电梯门关闭之前,又有人来了,你会继续开门;
这样一直进行下去,你可能需要等待几分钟,最终没人进电梯了,才会关闭电梯门,然后上楼。
所以debounce的作用是,当调用动作触发一段时间后,才会执行该动作,若在这段时间间隔内又调用此动作则将重新计算时间间隔。
throttle
假设你正在乘电梯上楼,当电梯门关闭之前发现有人也要乘电梯,礼貌起见,你会按下开门开关,然后等他进电梯;
但是,你是个没耐心的人,你最多只会等待电梯停留一分钟;
在这一分钟内,你会开门让别人进来,但是过了一分钟之后,你就会关门,让电梯上楼。
所以throttle的作用是,预先设定一个执行周期,当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新的时间周期。
二、简单实现:
1、首先来看看debounce的实现,根据前面对debounce的描述:
var debounce = function(action, delay) {
var timer = null;
return function() {
var self = this,
args = arguments;
clearTimeout(timer);
timer = setTimeout(function() {
action.apply(self, args)
}, delay);
}
}
// example
function resizeHandler() {
console.log("resize");
}
window.onresize = debounce(resizeHandler, 300);
可以在Chrome中运行下面的代码,看看debounce的效果,代码Github链接
2、throttle实现
throttle跟debounce的最大不同就是,throttle会有一个阀值,当到达阀值的时候action必定会执行一次。所以throttle的实现可以基于前面的debounce的实现,只需要加上一个阀值,代码Github链接:
var throttleV1 = function(action, delay, mustRunDelay) {
var timer = null,
startTime;
return function() {
var self = this,
args = arguments,
currTime = new Date();
clearTimeout(timer);
if(!startTime) {
startTime = currTime;
}
if(currTime - startTime >= mustRunDelay) {
action.apply(self, args);
startTime = currTime;
}
else {
timer = setTimeout(function() {
action.apply(self, args);
}, delay);
}
};
};
其实,对于上面的实现可以进心简化,只是通过闭包维护一个开始的时间:
var throttleV2 = function(action, delay){
var statTime = 0;
return function() {
var currTime = +new Date();
if (currTime - statTime > delay) {
action.apply(this, arguments);
statTime = currTime ;
}
}
}
// example
function resizeHandler() {
console.log("resize");
}
window.onresize = throttleV2(resizeHandler, 300);
通过前面的介绍,应该对debounce和throttle有一个直观的认识了:
了解了throttle和debounce之后,下面看看他们的常用场景:
debounce
throttle
对于键盘事件,当用户输入非常频繁,但是我们又必须要在一定时间内(阀值)内执行处理函数的时候,就可以使用throttle
。例如,一些网页游戏的键盘事件对于鼠标移动和窗口滚动,鼠标的移动和窗口的滚动会带来大量的事件,但是在一段时间内又必须看到页面的效果。例如对于可以拖动的div,如果使用debounce,那么div会在拖动停止后一下子跳到目标位置;这时就需要使用throttle
用户向下滚动无限滚动页面,需要检查滚动位置距底部多远,如果邻近底部了,我们可以发 AJAX 请求获取更多的数据插入到页面中。使用debounce 就不适用了,只有当用户停止滚动的时候它才会触发。实际用户滚动至邻近底部时,我们就想获取内容。所以,使用throttle 可以保证我们不断检查距离底部有多远。
三、示例:
1、向下无限滚动页面:
throttle测试
Infinite scrolling throttled
Block 1
Block 2
Block 3
Block 4
效果如下:
参考:
https://www.cnblogs.com/wilber2013/p/5893426.html