1绪氛、什么是防抖與節(jié)流涝影?
節(jié)流 : 通俗的講就是擰緊水龍頭讓水少流一點,但是不是不讓水流了序目。想象一下在現(xiàn)實生活中有時候我們需要接一桶水伯襟,接水的同時不想一直站在那等著姆怪,可能要離開一會去干一點別的事請舆瘪,讓水差不多流滿一桶水的時候再回來红伦,這個時候昙读,不能把水龍頭開的太大,不然還沒回來水就已經(jīng)滿了唠叛,浪費了好多水沮稚,這時候就需要節(jié)流艺沼,讓自己回來的時候水差不多滿了。那在JS里有沒有這種情況呢蕴掏,典型的場景是監(jiān)聽頁面的scoll事件障般,或者監(jiān)聽鼠標(biāo)的mousemove事件,這些事件對應(yīng)的處理方法相當(dāng)于水盛杰,由于scroll和mousemove在鼠標(biāo)移動的時候會被瀏覽器頻繁的觸發(fā)挽荡,會導(dǎo)致對應(yīng)的事件也會被頻繁的觸發(fā)(水流的太快了),這樣就會造成很大的瀏覽器資源開銷即供,而且好多中間的處理是不必要的定拟,這樣就會造成瀏覽器卡頓的現(xiàn)象,這時候就需要節(jié)流逗嫡,如何節(jié)流呢?我們無法做到讓瀏覽器不去觸發(fā)對應(yīng)的事件驱证,但是可以做到讓處理事件的方法執(zhí)行頻率減少延窜,從而減少對應(yīng)的處理開銷。
防抖 : 最早接觸這個詞應(yīng)該是在高中物理里面學(xué)到的雷滚,有時候開關(guān)在真正閉合之前可能會發(fā)生一些抖動現(xiàn)象需曾,如果抖動的明顯的話,對應(yīng)的小燈泡可能會閃爍祈远,把燈泡閃壞了不重要呆万,萬一把眼睛再給閃壞了可就麻煩了,這個時候就有去抖電路的出現(xiàn)车份。而在我們的頁面里谋减,也有這種情況,假設(shè)我們的一個輸入框扫沼,輸入內(nèi)容的同時可能會去后臺查詢對應(yīng)的聯(lián)想詞出爹,如果用戶輸入的同時庄吼,頻繁的觸發(fā)input事件,然后頻繁的向后臺發(fā)送請求严就,那么直到用戶輸入完成時总寻,之前的請求都應(yīng)該是多余的,假設(shè)網(wǎng)絡(luò)慢一點梢为,后臺返回的數(shù)據(jù)比較慢渐行,那么顯示的聯(lián)想詞可能會出現(xiàn)頻繁的變換,直到最后的一個請求返回铸董。這個時候就可以在一定時間內(nèi)監(jiān)聽是否再次輸入祟印,如果沒有再次輸入則認(rèn)為本次輸入完成,發(fā)送請求粟害,否則就是判定用戶仍在輸入蕴忆,不發(fā)送請求。
去抖和節(jié)流是不同的悲幅,因為節(jié)流雖然中間的處理函數(shù)被限制了套鹅,但是只是減少了頻率,而防抖則把中間的處理函數(shù)全部過濾掉了夺艰,只執(zhí)行規(guī)定時間內(nèi)的最后一個事件
2芋哭、實例
假設(shè)頁面有一個返回頂部的按鈕,我們的需求是:"監(jiān)聽瀏覽器滾動事件郁副,返回當(dāng)前滾動條與頂部的距離"
function showTop() {
const scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
console.log('滾動條位置:' + scrollTop);
}
window.onscroll = showTop
在運(yùn)行的時候會發(fā)現(xiàn)存在一個問題:這個函數(shù)的默認(rèn)執(zhí)行頻率,太豌习!高存谎!了!肥隆。高到什么程度呢既荚?以chrome為例,我們可以點擊選中一個頁面的滾動條栋艳,然后點擊一次鍵盤的【向下方向鍵】恰聘,會發(fā)現(xiàn)函數(shù)執(zhí)行了8-9次!
防抖
基于上述場景吸占,首先提出第一種思路:在第一次觸發(fā)事件時晴叨,不立即執(zhí)行函數(shù),而是給出一個期限值比如200ms矾屯,然后:
- 如果在200ms內(nèi)沒有再次觸發(fā)滾動事件兼蕊,那么就執(zhí)行函數(shù)
- 如果在200ms內(nèi)再次觸發(fā)滾動事件,那么當(dāng)前的計時取消件蚕,重新開始計時
效果:如果短時間內(nèi)大量觸發(fā)同一事件孙技,只會執(zhí)行一次函數(shù)产禾。
實現(xiàn):既然前面都提到了計時,那實現(xiàn)的關(guān)鍵就在于setTimeout這個函數(shù)牵啦,由于還需要一個變量來保存計時亚情,考慮維護(hù)全局純凈,可以借助閉包來實現(xiàn):
/*
* fn [function] 需要防抖的函數(shù)
* delay [number] 毫秒哈雏,防抖期限值
*/
function debounce(fn,delay){
let timer = null //借助閉包
return function() {
if(timer){
clearTimeout(timer) //進(jìn)入該分支語句势似,說明當(dāng)前正在一個計時過程中,并且又觸發(fā)了相同事件僧著。所以要取消當(dāng)前的計時履因,重新開始計時
}
timer = setTimeout(fn,delay)
}
}
function showTop () {
const scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
console.log('滾動條位置:' + scrollTop);
}
window.onscroll = debounce(showTop,3000) // 為了方便觀察效果我們?nèi)€大點的間斷值,實際使用根據(jù)需要來配置
此時會發(fā)現(xiàn)盹愚,必須在停止?jié)L動3秒以后栅迄,才會打印出滾動條位置。到這里皆怕,已經(jīng)把防抖實現(xiàn)了毅舆,現(xiàn)在給出定義:
- 對于短時間內(nèi)連續(xù)觸發(fā)的事件(上面的滾動事件),防抖的含義就是讓某個時間期限(如上面的3000毫秒)內(nèi)愈腾,事件處理函數(shù)只執(zhí)行一次憋活。
節(jié)流
繼續(xù)思考,使用上面的防抖方案來處理問題的結(jié)果是:
- 如果在限定時間段內(nèi)虱黄,不斷觸發(fā)滾動事件(比如某個用戶閑著無聊悦即,按住滾動不斷的拖來拖去),只要不停止觸發(fā)橱乱,理論上就永遠(yuǎn)不會輸出當(dāng)前距離頂部的距離辜梳。
- 但是如果產(chǎn)品同學(xué)的期望處理方案是:即使用戶不斷拖動滾動條,也能在某個時間間隔之后給出反饋呢泳叠?
其實很簡單:我們可以設(shè)計一種類似控制閥門一樣定期開放的函數(shù)作瞄,也就是讓函數(shù)執(zhí)行一次后,在某個時間段內(nèi)暫時失效危纫,過了這段時間后再重新激活(類似于技能冷卻時間)宗挥。
效果:如果短時間內(nèi)大量觸發(fā)同一事件,那么在函數(shù)執(zhí)行一次之后种蝶,該函數(shù)在指定的時間期限內(nèi)不再工作契耿,直至過了這段時間才重新生效。
實現(xiàn) 這里借助setTimeout來做一個簡單的實現(xiàn)蛤吓,加上一個狀態(tài)位valid來表示當(dāng)前函數(shù)是否處于工作狀態(tài):
function throttle(fn,delay){
let valid = true
return function() {
if(!valid){
//休息時間 暫不工作
return false
}
// 工作時間宵喂,執(zhí)行函數(shù)并且在間隔期內(nèi)把狀態(tài)位設(shè)為無效
valid = false
setTimeout(() => {
fn()
valid = true;
}, delay)
}
}
/* 請注意,節(jié)流函數(shù)并不止上面這種實現(xiàn)方案,
例如可以完全不借助setTimeout会傲,可以把狀態(tài)位換成時間戳锅棕,然后利用時間戳差值是否大于指定間隔時間來做判定拙泽。
也可以直接將setTimeout的返回的標(biāo)記當(dāng)做判斷條件-判斷當(dāng)前定時器是否存在,如果存在表示還在冷卻裸燎,并且在執(zhí)行fn之后消除定時器表示激活顾瞻,原理都一樣
*/
// 以下照舊
function showTop () {
var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
console.log('滾動條位置:' + scrollTop);
}
window.onscroll = throttle(showTop,1000)
- 如果一直拖著滾動條進(jìn)行滾動,那么會以1s的時間間隔德绿,持續(xù)輸出當(dāng)前位置和頂部的距離
3荷荤、其他應(yīng)用場景舉例
講完了這兩個技巧,下面介紹一下平時開發(fā)中常遇到的場景:
- 搜索框input事件移稳,例如要支持輸入實時搜索可以使用節(jié)流方案(間隔一段時間就必須查詢相關(guān)內(nèi)容)蕴纳,或者實現(xiàn)輸入間隔大于某個值(如500ms),就當(dāng)做用戶輸入完成个粱,然后開始搜索古毛,具體使用哪種方案要看業(yè)務(wù)需求。
- 頁面resize事件都许,常見于需要做頁面適配的時候稻薇。需要根據(jù)最終呈現(xiàn)的頁面情況進(jìn)行dom渲染(這種情形一般是使用防抖,因為只需要判斷最后一次的變化情況)