前端性能優(yōu)化-防抖和節(jié)流
前言
防抖和節(jié)流嚴(yán)格算起來(lái)應(yīng)該屬于性能優(yōu)化的知識(shí),但實(shí)際上遇到的頻率相當(dāng)高,處理不當(dāng)或者放任不管就容易引起瀏覽器卡死。所以還是很有必要早點(diǎn)掌握的。
據(jù)說(shuō)阿里有一道面試題就是談?wù)労瘮?shù)節(jié)流和函數(shù)防抖。
防抖(debounce)
函數(shù)防抖(debounce):當(dāng)持續(xù)觸發(fā)事件時(shí),在設(shè)置的周期內(nèi)沒(méi)有再觸發(fā)事件,事件處理函數(shù)才會(huì)執(zhí)行一次,如果設(shè)定的周期沒(méi)有結(jié)束,又一次觸發(fā)了事件,就重新開(kāi)始延時(shí)。
為了有個(gè)直觀的對(duì)比,我們先看下沒(méi)有使用debounce技術(shù)的click事件:我們看到,當(dāng)用戶頻繁點(diǎn)擊button按鈕時(shí),控制臺(tái)會(huì)頻繁的輸出結(jié)果,這種頻繁調(diào)用事件處理程序,會(huì)加重瀏覽器的負(fù)擔(dān),導(dǎo)致用戶體驗(yàn)非常糟糕。
為了解決上述問(wèn)題,我們?cè)诰幋a中可以使用debounce防抖技術(shù)。
防抖原理:是維護(hù)一個(gè)計(jì)時(shí)器,在規(guī)定的delay時(shí)間后觸發(fā)函數(shù),但是在delay時(shí)間內(nèi)再次觸發(fā)的話,就會(huì)取消之前的計(jì)時(shí)器而重新設(shè)置。這樣一來(lái),只有最后一次操作能被觸發(fā)。
看一個(gè)??(栗子):
function debounce(fn, delay) {
var timer = null;
return function() {
// 清除已存在的定時(shí)器
timer && clearTimeout(timer)
timer = setTimeout(function() {
fn.apply(this)
}, delay)
}
}
let $btn = document.getElementById('btn');
var fn = function() {
console.log ('防抖旨在時(shí)間段內(nèi)只觸發(fā)最后一次執(zhí)行' + new Date(Date.now()));
}
$btn.onclick = debounce(fn, 1000);
如圖,持續(xù)觸發(fā)click
事件時(shí),并不會(huì)每次觸發(fā)都會(huì)執(zhí)行事件處理函數(shù),當(dāng)在設(shè)置的周期1000 ms
內(nèi)沒(méi)有再觸發(fā)click
事件時(shí),才會(huì)延時(shí)觸發(fā)click
事件。
節(jié)流(throttle)
函數(shù)節(jié)流(throttle):函數(shù)執(zhí)行一次后,只有在大于設(shè)置的執(zhí)行周期后才會(huì)執(zhí)行第二次。持續(xù)觸發(fā)事件時(shí),保證一定時(shí)間段內(nèi)只調(diào)用一次事件處理函數(shù)。
throttle
翻譯為節(jié)流閥
,我們可以想象成我們水龍頭放水,閥門(mén)一打開(kāi),水嘩嘩的往下流,秉著勤儉節(jié)約的優(yōu)良傳統(tǒng)美德,我們要把水龍頭關(guān)小點(diǎn),最好是如我們心意按照一定規(guī)律在某個(gè)時(shí)間間隔內(nèi)一滴一滴的往下滴。
同樣我們先看一個(gè)沒(méi)有使用throttle
技術(shù)的scroll
事件,如下圖:
這種頻繁的scroll
操作都會(huì)給瀏覽器帶來(lái)沉重的負(fù)擔(dān),接下來(lái)我們看下如何使用throttle
技術(shù)。
節(jié)流原理:是記錄上次執(zhí)行的時(shí)間戳lastTime,每次觸發(fā)事件時(shí)記錄當(dāng)前執(zhí)行的時(shí)間戳nowTime
,然后判斷nowTime與lastTime的差值是否大于設(shè)定的周期delay,如果大于,則執(zhí)行回調(diào),并更新上次執(zhí)行的時(shí)間戳,從而循環(huán)。持續(xù)觸發(fā)事件時(shí),保證一定時(shí)間段內(nèi)觸發(fā)事件處理函數(shù)的頻率。
再看一個(gè)??:
function throttle(fn, delay) {
// 記錄上次觸發(fā)的時(shí)間戳
var lastTime = 0;
return function() {
// 記錄當(dāng)前觸發(fā)的時(shí)間戳
var nowTime = Date.now();
// 如果當(dāng)前觸發(fā)與上次觸發(fā)的時(shí)間差值 大于 設(shè)置的周期則允許執(zhí)行
if (nowTime - lastTime > delay) {
fn.call(this);
// 更新時(shí)間戳
lastTime = nowTime;
}
}
}
document.onscroll = function () {
console.log ('節(jié)流旨在時(shí)間段內(nèi)控制觸發(fā)的頻率'+new Date(Date.now()))
}
如下圖,持續(xù)觸發(fā)scroll事件時(shí),并不立即執(zhí)行處理函數(shù),當(dāng)當(dāng)前觸發(fā)與上次觸發(fā)的時(shí)間差值大于設(shè)置的周期時(shí)才會(huì)執(zhí)行。應(yīng)用場(chǎng)景
上面介紹了防抖(debounce) 和 節(jié)流(throttle) 的原理和實(shí)現(xiàn)方式。
下面簡(jiǎn)單列出兩者的應(yīng)用場(chǎng)景都有哪些:
防抖(debounce)應(yīng)用場(chǎng)景:
- 每個(gè)調(diào)整大小/滾動(dòng)都會(huì)觸發(fā)統(tǒng)計(jì)事件。
- 驗(yàn)證文本輸入(在連續(xù)文本輸入后,發(fā)送Ajax請(qǐng)求進(jìn)行驗(yàn)證)。
- 監(jiān)視滾動(dòng)scroll事件(在添加去抖動(dòng)后滾動(dòng),只有在用戶停止?jié)L動(dòng)后才會(huì)確定它是否已到達(dá)頁(yè)面底部)。
節(jié)流(throttle)應(yīng)用場(chǎng)景:
- 實(shí)現(xiàn)DOM元素的拖放功能mousemove。
- 搜索關(guān)聯(lián)keyup。
- 計(jì)算鼠標(biāo)移動(dòng)距離mousemove。
- 畫(huà)布模擬草圖功能mousemove。
- 射擊游戲中的 mousedown/keydown事件(每單位時(shí)間只能發(fā)射一顆- 子彈)。
- 監(jiān)視滾動(dòng)scroll事件(添加節(jié)流后,只要滾動(dòng)頁(yè)面,就會(huì)每隔一段時(shí)間才會(huì)計(jì)算)。
總結(jié)
- 函數(shù)防抖和函數(shù)節(jié)流都是防止某一時(shí)間頻繁觸發(fā),但是這兩兄弟之間的原理卻不一樣。
- 函數(shù)防抖是某一段時(shí)間內(nèi)只執(zhí)行一次,而函數(shù)節(jié)流是間隔時(shí)間執(zhí)行。
往期推薦
?話題
什么樣的答案終身難忘?學(xué)生時(shí)代關(guān)于記憶經(jīng)常能聽(tīng)見(jiàn)兩種論調(diào):
1.死記硬背:見(jiàn)效快,但也忘得快,且一般不會(huì)靈活運(yùn)用(指標(biāo)不治本)
2.理解性記憶:見(jiàn)效慢,但記憶持久且會(huì)靈活運(yùn)用(治標(biāo)又治本)
如果是你,你愿意pick哪種?