前端性能優(yōu)化-防抖和節(jié)流
前言
防抖和節(jié)流嚴(yán)格算起來應(yīng)該屬于性能優(yōu)化的知識,但實際上遇到的頻率相當(dāng)高,處理不當(dāng)或者放任不管就容易引起瀏覽器卡死。所以還是很有必要早點掌握的。
據(jù)說阿里有一道面試題就是談?wù)労瘮?shù)節(jié)流和函數(shù)防抖。
防抖(debounce)
函數(shù)防抖(debounce):當(dāng)持續(xù)觸發(fā)事件時,在設(shè)置的周期內(nèi)沒有再觸發(fā)事件,事件處理函數(shù)才會執(zhí)行一次,如果設(shè)定的周期沒有結(jié)束,又一次觸發(fā)了事件,就重新開始延時。
為了有個直觀的對比,我們先看下沒有使用debounce技術(shù)的click事件:
我們看到,當(dāng)用戶頻繁點擊button按鈕時,控制臺會頻繁的輸出結(jié)果,這種頻繁調(diào)用事件處理程序,會加重瀏覽器的負(fù)擔(dān),導(dǎo)致用戶體驗非常糟糕。
為了解決上述問題,我們在編碼中可以使用debounce防抖技術(shù)。
防抖原理:是維護一個計時器,在規(guī)定的delay時間后觸發(fā)函數(shù),但是在delay時間內(nèi)再次觸發(fā)的話,就會取消之前的計時器而重新設(shè)置。這樣一來,只有最后一次操作能被觸發(fā)。
看一個??(栗子):
function debounce(fn, delay) {
var timer = null;
return function() {
// 清除已存在的定時器
timer && clearTimeout(timer)
timer = setTimeout(function() {
fn.apply(this)
}, delay)
}
}
let $btn = document.getElementById('btn');
var fn = function() {
console.log ('防抖旨在時間段內(nèi)只觸發(fā)最后一次執(zhí)行' + new Date(Date.now()));
}
$btn.onclick = debounce(fn, 1000);
如圖,持續(xù)觸發(fā)click事件時,并不會每次觸發(fā)都會執(zhí)行事件處理函數(shù),當(dāng)在設(shè)置的周期1000 ms內(nèi)沒有再觸發(fā)click事件時,才會延時觸發(fā)click事件。

節(jié)流(throttle)
函數(shù)節(jié)流(throttle):函數(shù)執(zhí)行一次后,只有在大于設(shè)置的執(zhí)行周期后才會執(zhí)行第二次。持續(xù)觸發(fā)事件時,保證一定時間段內(nèi)只調(diào)用一次事件處理函數(shù)。
throttle翻譯為節(jié)流閥,我們可以想象成我們水龍頭放水,閥門一打開,水嘩嘩的往下流,秉著勤儉節(jié)約的優(yōu)良傳統(tǒng)美德,我們要把水龍頭關(guān)小點,最好是如我們心意按照一定規(guī)律在某個時間間隔內(nèi)一滴一滴的往下滴。
同樣我們先看一個沒有使用throttle技術(shù)的scroll事件,如下圖:


這種頻繁的scroll操作都會給瀏覽器帶來沉重的負(fù)擔(dān),接下來我們看下如何使用throttle技術(shù)。
節(jié)流原理:是記錄上次執(zhí)行的時間戳lastTime,每次觸發(fā)事件時記錄當(dāng)前執(zhí)行的時間戳nowTime,然后判斷nowTime與lastTime的差值是否大于設(shè)定的周期delay,如果大于,則執(zhí)行回調(diào),并更新上次執(zhí)行的時間戳,從而循環(huán)。持續(xù)觸發(fā)事件時,保證一定時間段內(nèi)觸發(fā)事件處理函數(shù)的頻率。
再看一個??:
function throttle(fn, delay) {
// 記錄上次觸發(fā)的時間戳
var lastTime = 0;
return function() {
// 記錄當(dāng)前觸發(fā)的時間戳
var nowTime = Date.now();
// 如果當(dāng)前觸發(fā)與上次觸發(fā)的時間差值 大于 設(shè)置的周期則允許執(zhí)行
if (nowTime - lastTime > delay) {
fn.call(this);
// 更新時間戳
lastTime = nowTime;
}
}
}
document.onscroll = function () {
console.log ('節(jié)流旨在時間段內(nèi)控制觸發(fā)的頻率'+new Date(Date.now()))
}
如下圖,持續(xù)觸發(fā)scroll事件時,并不立即執(zhí)行處理函數(shù),當(dāng)當(dāng)前觸發(fā)與上次觸發(fā)的時間差值大于設(shè)置的周期時才會執(zhí)行。
應(yīng)用場景
上面介紹了防抖(debounce) 和 節(jié)流(throttle) 的原理和實現(xiàn)方式。
下面簡單列出兩者的應(yīng)用場景都有哪些:
防抖(debounce)應(yīng)用場景:
- 每個調(diào)整大小/滾動都會觸發(fā)統(tǒng)計事件。
- 驗證文本輸入(在連續(xù)文本輸入后,發(fā)送Ajax請求進行驗證)。
- 監(jiān)視滾動scroll事件(在添加去抖動后滾動,只有在用戶停止?jié)L動后才會確定它是否已到達頁面底部)。
節(jié)流(throttle)應(yīng)用場景:
- 實現(xiàn)DOM元素的拖放功能mousemove。
- 搜索關(guān)聯(lián)keyup。
- 計算鼠標(biāo)移動距離mousemove。
- 畫布模擬草圖功能mousemove。
- 射擊游戲中的 mousedown/keydown事件(每單位時間只能發(fā)射一顆- 子彈)。
- 監(jiān)視滾動scroll事件(添加節(jié)流后,只要滾動頁面,就會每隔一段時間才會計算)。
總結(jié)
- 函數(shù)防抖和函數(shù)節(jié)流都是防止某一時間頻繁觸發(fā),但是這兩兄弟之間的原理卻不一樣。
- 函數(shù)防抖是某一段時間內(nèi)只執(zhí)行一次,而函數(shù)節(jié)流是間隔時間執(zhí)行。
往期推薦
?話題
什么樣的答案終身難忘?學(xué)生時代關(guān)于記憶經(jīng)常能聽見兩種論調(diào):
1.死記硬背:見效快,但也忘得快,且一般不會靈活運用(指標(biāo)不治本)
2.理解性記憶:見效慢,但記憶持久且會靈活運用(治標(biāo)又治本)
如果是你,你愿意pick哪種?