事件稀释是一个在实际开发过程中经常遇到的问题,例如一次鼠标滚动可能触发几十次滚动事件,当我们在懒加载的时候如果不做稀释的话,很可能用户滚动一下鼠标就会发出几十次ajax
请求,这简直是一场灾难。
稀释原理
比较常见的解决方法是 Debounce
和Throttle
。两者的目的都是为了稀释事件,但是原理有所不同。
Debounce
函数的理念是“推迟执行”,即把N次连续事件只执行一次。
Throttle
函数的理念是“时间稀释”,即N次连续事件在规定时间内最多执行一次。
举个例子来说,如果把事件触发比作是乘客,而事件的执行是公交车的话,当我们连续滚动屏幕不停触发'scrolling'事件的时候,就相当于来一个乘客,上一辆公交车,就发车,一辆公交车只搭载一名乘客就出发了。
而 Debounce
函数则是“推迟发车”,即我们规定公交车等1分钟,每来一名乘客,公交车就多等一分钟,这样只要乘客到达的时间间隔小于1分钟,公交车就会无限等待下去,直到某一次两名乘客到达的时间间隔大于1分钟,公交车发车了。如果不来乘客,公交车就不发车。
Throttle
函数则是对于时间的稀释,对于Throttle
的公交车,即使有乘客,那么每一分钟最多只发车一次,即使上一名乘客是59秒达到而下一名乘客是1分01秒到达,公交车依旧会在1分钟时准时发车,下一名乘客只能等待下一班车了。同样,如果不来乘客,公交车就不会发车。
下面我希望能通过一些函数Demo来说明Debounce
与Throttle
Debounce
这里给出了一个Debounce函数的示例,我是用class的方式实现的
class Debounce {
handleFunction: Function;
time: number | string;
handler: number;
context: object;
constructor(handleFunction: Function, time: number | string, context?:Object) {
this.handleFunction = handleFunction || (() => { });
this.time = +time || 0;
// 解决this指针作用域
this.context = context || {};
this.carry = this.carry.bind(this);
this.clear = this.clear.bind(this);
// setTimeout 句柄
this.handler = 0;
}
carry(): void {
if (this.handler) {
this.clear();
}
this.handler = setTimeout(() => {
this.clear();
this.handleFunction.apply(this.context);
}, this.time);
}
clear(): void {
clearTimeout(this.handler);
this.handler = 0;
}
}
核心是Debounce.prototype.carry
函数,我们可以看到,在每次调用的时候首先检测当前有没有正在执行的Debounce函数,如果有的话,则取消上一次的执行,再添加一次新的延迟执行函数,从而达到上述效果。
如果你想测试一下上面的函数,可以用以下方式调用:
import Debounce from 'path/to/debounce';
const a = new Debounce(function a() {
console.log("hi");
}, 1000);
window.addEventListener('scroll', a.carry);
再新建个HTML,然后把body的height设置大点就OK了。
Throttle
同样给出Throttle
函数
class Throttle {
handleFunction: Function;
time: number | string;
handler: number;
context: object;
constructor(handleFunction: Function, time: number | string, context?:Object) {
this.handleFunction = handleFunction || (() => { });
this.time = +time || 0;
// 解决this指针作用域
this.context = context || {};
this.carry = this.carry.bind(this);
this.clear = this.clear.bind(this);
// setTimeout 句柄
this.handler = 0;
}
carry(): any {
if (this.handler) {
return;
}
this.handler = setInterval(() => {
this.handleFunction.apply(this.context);
this.clear();
}, this.time);
}
clear(): void {
clearInterval(this.handler);
this.handler = 0;
}
}
export default Throttle;
可以看到其中的carry
函数在规定时间内最多执行一次。
最近在项目中遇到的一些坑
其实这篇文章在写出以后确实在项目中遇到了一些问题,所以再看这篇文章写的还是有些不太全面。对于事件稀释,Throttle和Debounce都不过是稀释方法的一种。大致思想都是在对于在某一段时间内频繁触发的事件合并为一次进行请求。但是实际业务中的事件稀释方式要多得多。对于长页面的lazy load,不一定需要按时间为单位,亦可以按请求为单位,因为我们需要避免的事一秒发出几十次请求。对于不同的场景,我们要分析具体需要稀释的事情是什么。在错误的场景使用不合适的稀释方式可能反而会造成性能问题。事件稀释的精髓在于对于“事件”的稀释,不要拘泥于形式。