事件稀釋是一個(gè)在實(shí)際開發(fā)過程中經(jīng)常遇到的問題耕餐,例如一次鼠標(biāo)滾動(dòng)可能觸發(fā)幾十次滾動(dòng)事件肠缔,當(dāng)我們?cè)趹屑虞d的時(shí)候如果不做稀釋的話,很可能用戶滾動(dòng)一下鼠標(biāo)就會(huì)發(fā)出幾十次ajax
請(qǐng)求槽华,這簡(jiǎn)直是一場(chǎng)災(zāi)難硼莽。
稀釋原理
比較常見的解決方法是 Debounce
和Throttle
煮纵。兩者的目的都是為了稀釋事件行疏,但是原理有所不同酿联。
Debounce
函數(shù)的理念是“推遲執(zhí)行”,即把N次連續(xù)事件只執(zhí)行一次周崭。
Throttle
函數(shù)的理念是“時(shí)間稀釋”喳张,即N次連續(xù)事件在規(guī)定時(shí)間內(nèi)最多執(zhí)行一次销部。
舉個(gè)例子來說,如果把事件觸發(fā)比作是乘客酱虎,而事件的執(zhí)行是公交車的話读串,當(dāng)我們連續(xù)滾動(dòng)屏幕不停觸發(fā)'scrolling'事件的時(shí)候撒妈,就相當(dāng)于來一個(gè)乘客,上一輛公交車胀茵,就發(fā)車琼娘,一輛公交車只搭載一名乘客就出發(fā)了附鸽。
而 Debounce
函數(shù)則是“推遲發(fā)車”,即我們規(guī)定公交車等1分鐘熄浓,每來一名乘客赌蔑,公交車就多等一分鐘竟秫,這樣只要乘客到達(dá)的時(shí)間間隔小于1分鐘,公交車就會(huì)無限等待下去趾浅,直到某一次兩名乘客到達(dá)的時(shí)間間隔大于1分鐘,公交車發(fā)車了浅侨。如果不來乘客证膨,公交車就不發(fā)車椎例。
Throttle
函數(shù)則是對(duì)于時(shí)間的稀釋,對(duì)于Throttle
的公交車脖祈,即使有乘客盖高,那么每一分鐘最多只發(fā)車一次眼虱,即使上一名乘客是59秒達(dá)到而下一名乘客是1分01秒到達(dá),公交車依舊會(huì)在1分鐘時(shí)準(zhǔn)時(shí)發(fā)車撞蚕,下一名乘客只能等待下一班車了过牙。同樣,如果不來乘客刀疙,公交車就不會(huì)發(fā)車谦秧。
下面我希望能通過一些函數(shù)Demo來說明Debounce
與Throttle
Debounce
這里給出了一個(gè)Debounce函數(shù)的示例,我是用class的方式實(shí)現(xiàn)的
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
函數(shù),我們可以看到缘挑,在每次調(diào)用的時(shí)候首先檢測(cè)當(dāng)前有沒有正在執(zhí)行的Debounce函數(shù),如果有的話,則取消上一次的執(zhí)行,再添加一次新的延遲執(zhí)行函數(shù)焕窝,從而達(dá)到上述效果它掂。
如果你想測(cè)試一下上面的函數(shù)溯泣,可以用以下方式調(diào)用:
import Debounce from 'path/to/debounce';
const a = new Debounce(function a() {
console.log("hi");
}, 1000);
window.addEventListener('scroll', a.carry);
再新建個(gè)HTML垃沦,然后把body的height設(shè)置大點(diǎn)就OK了。
Throttle
同樣給出Throttle
函數(shù)
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
函數(shù)在規(guī)定時(shí)間內(nèi)最多執(zhí)行一次靶剑。
最近在項(xiàng)目中遇到的一些坑
其實(shí)這篇文章在寫出以后確實(shí)在項(xiàng)目中遇到了一些問題桩引,所以再看這篇文章寫的還是有些不太全面收夸。對(duì)于事件稀釋,Throttle和Debounce都不過是稀釋方法的一種厘灼。大致思想都是在對(duì)于在某一段時(shí)間內(nèi)頻繁觸發(fā)的事件合并為一次進(jìn)行請(qǐng)求序苏。但是實(shí)際業(yè)務(wù)中的事件稀釋方式要多得多忱详。對(duì)于長頁面的lazy load,不一定需要按時(shí)間為單位匈睁,亦可以按請(qǐng)求為單位航唆,因?yàn)槲覀冃枰苊獾氖乱幻氚l(fā)出幾十次請(qǐng)求。對(duì)于不同的場(chǎng)景粪狼,我們要分析具體需要稀釋的事情是什么。在錯(cuò)誤的場(chǎng)景使用不合適的稀釋方式可能反而會(huì)造成性能問題再榄。事件稀釋的精髓在于對(duì)于“事件”的稀釋,不要拘泥于形式嗅蔬。