網(wǎng)頁(yè)生成的時(shí)候司顿,至少會(huì)渲染(Layout+Paint)一次。用戶訪問的過程中兄纺,還會(huì)不斷重新的重排(reflow)和重繪(repaint)大溜。用戶 scroll 和 resize 行為(即是滑動(dòng)頁(yè)面和改變窗口大小)會(huì)導(dǎo)致頁(yè)面不斷的重新渲染囤热。scroll 事件本身會(huì)觸發(fā)頁(yè)面的重新渲染猎提,同時(shí) scroll 事件的 handler 又會(huì)被高頻度的觸發(fā), 因此事件的 handler 內(nèi)部不應(yīng)該有復(fù)雜操作,例如 DOM 操作就不應(yīng)該放在事件處理中。針對(duì)此類高頻度觸發(fā)事件問題
(例如頁(yè)面 scroll 锨苏,屏幕 resize疙教,監(jiān)聽用戶輸入等)
,下面介紹兩種常用的解決方法防抖
和節(jié)流
伞租。
感謝博主分享 :
ChokCoco
Alon's Blog
防抖(Debouncing)
防抖技術(shù)即是可以把多個(gè)順序地調(diào)用合并成一次贞谓,也就是在一定時(shí)間內(nèi),規(guī)定事件被觸發(fā)的次數(shù)葵诈。
window.addEventListener('scroll',debounce(realFunc,500))
//優(yōu)化滾動(dòng)效果防抖動(dòng)
function debounce(fn,time){
var timeout = null;
return function(){
clearTimeout(timeout)
timeout = setTimeout(fn,time)
}
}
function realFunc(){
console.log('this is real callback function')
}
}
原理:
首先裸弦,我們?yōu)閟croll事件綁定處理函數(shù),這時(shí)debounce函數(shù)會(huì)立即調(diào)用作喘,
因此給scroll事件綁定的函數(shù)實(shí)際上是debounce內(nèi)部返回的函數(shù)每一次事件被觸發(fā)理疙,都會(huì)清除當(dāng)前的 timer 然后重新設(shè)置超時(shí)調(diào)用。
這就會(huì)導(dǎo)致每一次高頻事件都會(huì)取消前一次的超時(shí)調(diào)用泞坦,導(dǎo)致事件處理程序不能被觸發(fā)只有當(dāng)高頻事件停止窖贤,最后一次事件觸發(fā)的超時(shí)調(diào)用才能在delay時(shí)間后執(zhí)行
繼續(xù)優(yōu)化的寫法:不希望非要等到事件停止觸發(fā)后才執(zhí)行,我希望立刻執(zhí)行函數(shù)贰锁,然后等到停止觸發(fā) n 秒后赃梧,才可以重新觸發(fā)執(zhí)行。
window.addEventListener('scroll',debounce(realFunc,500,true))
//優(yōu)化滾動(dòng)效果防抖動(dòng)
function debounce(fn,time,immediate){
var timeout = null;
return function(){
if(timeout){
clearTimeout(timeout)
}
if(immediate){
//根據(jù)距離上次觸發(fā)操作的時(shí)間是否到達(dá)delay來(lái)決定是否要現(xiàn)在執(zhí)行函數(shù)
var doNow = !timeout
//每一次都重新設(shè)置timer豌熄,就是要保證每一次執(zhí)行的至少delay秒后才可以執(zhí)行
timeout = setTimeout(function(){
timeout = null
},time)
//立即執(zhí)行
if(doNow) fn()
}else{
timeout = setTimeout(fn,time)
}
console.log('after'+timeout)
}
}
function realFunc(){
console.log('this is real callback function')
}
}
Debounce 實(shí)例 : 調(diào)整大小的例子 調(diào)整桌面瀏覽器窗口大小的時(shí)候授嘀,會(huì)觸發(fā)很多次 resize 事件。
oninput實(shí)時(shí)搜索的demo
節(jié)流(Throttling)
防抖函數(shù)確實(shí)不錯(cuò)锣险,但是也存在問題蹄皱,譬如圖片的懶加載,我希望在下滑過程中圖片不斷的被加載出來(lái)囱持,而不是只有當(dāng)我停止下滑時(shí)候夯接,圖片才被加載出來(lái)。又或者下滑時(shí)候的數(shù)據(jù)的 ajax 請(qǐng)求加載也是同理纷妆。
這個(gè)時(shí)候盔几,我們希望即使頁(yè)面在不斷被滾動(dòng),但是滾動(dòng) handler 也可以以一定的頻率被觸發(fā)(譬如 250ms 觸發(fā)一次)掩幢,這類場(chǎng)景逊拍,就要用到另一種技巧,稱為節(jié)流函數(shù)(throttling)际邻。
節(jié)流函數(shù)芯丧,只允許一個(gè)函數(shù)在 X 毫秒內(nèi)執(zhí)行一次。
與防抖相比世曾,節(jié)流函數(shù)最主要的不同在于它保證在 X 毫秒內(nèi)至少執(zhí)行一次我們希望觸發(fā)的事件 handler缨恒。
與防抖相比,節(jié)流函數(shù)多了一個(gè) mustRun 屬性,代表 mustRun 毫秒內(nèi)骗露,必然會(huì)觸發(fā)一次 handler 岭佳,同樣是利用定時(shí)器,看看簡(jiǎn)單的示例:
window.addEventListener('scroll',throttle(realFunc,500,1000))
//優(yōu)化滾動(dòng)效果節(jié)流
function throttle(fn,time){
var timeout,
startTime = new Date()
return function(){
var context = this,
args = arguments,
curTime = new Date()萧锉,
remaining = time - (curTime - startTime);
clearTimeout(timeout)
if(remaining <=0){
fn.apply(context,args)
startTime = curTime
}else{
timeout = setTimeout(fn,remaining)
}
}
}
function realFunc(){
console.log('this is real callback function')
}
節(jié)流閥實(shí)例 : 無(wú)限滾動(dòng) 用戶向下滾動(dòng)無(wú)限滾動(dòng)頁(yè)面珊随,需要檢查滾動(dòng)位置距底部多遠(yuǎn),如果鄰近底部了柿隙,我們可以發(fā) AJAX 請(qǐng)求獲取更多的數(shù)據(jù)插入到頁(yè)面中叶洞。
下拉加載demo
使用 rAF(requestAnimationFrame)觸發(fā)滾動(dòng)事件
上面介紹的抖動(dòng)與節(jié)流實(shí)現(xiàn)的方式都是借助了定時(shí)器 setTimeout ,但是如果頁(yè)面只需要兼容高版本瀏覽器或應(yīng)用在移動(dòng)端禀崖,又或者頁(yè)面需要追求高精度的效果衩辟,那么可以使用瀏覽器的原生方法 rAF(requestAnimationFrame)。
簡(jiǎn)單而言帆焕,使用 requestAnimationFrame 來(lái)觸發(fā)滾動(dòng)事件惭婿,相當(dāng)于上面的:
throttle(func, xx, 1000/60) //xx 代表 xx ms內(nèi)不會(huì)重復(fù)觸發(fā)事件 handler