防抖和節(jié)流嚴格意義上是屬于性能優(yōu)化方面的知識,接下來使用實際應用場景詳細解釋防抖和節(jié)流验辞。
案例:
在滾動條滾動到距離頂部一定距離的時候,會出現一個返回頂部的標識。點擊就會執(zhí)行回到頂部的方法荣瑟。
在這個案例中就會用到監(jiān)聽瀏覽器滾輪事件,事件不停的返回距離頂部的距離摩泪,那么我們可以直接寫:
function showTop () {
var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
console.log('滾動條位置:' + scrollTop);
}
window.onscroll = showTop
這里我們就會發(fā)現一個問題笆焰,這個函數的默認執(zhí)行頻率太高了,瀏覽器的性能不應該消耗在這里见坑。
防抖(debounce)
針對這個案例以及場景嚷掠,提出第一個思路:
我們在第一次執(zhí)行這個事件時給一個期限,比如200ms,
- 如果在200ms內沒有觸發(fā)事件荞驴,我們就只執(zhí)行一次事件
- 如果在200ms內再次觸發(fā)這個事件不皆,我們就取消當前的計時,開啟新的計時
由此這個解決方案主要是讓這個事件在一定時間內只需要執(zhí)行一次熊楼,不會被重復的調用霹娄。
按照這個思路解決這個問題,我們就需要用到setTimeout函數,還有一個變量保存計時鲫骗,考慮維護全局純凈犬耻,我們使用閉包。
/*
* fn [function] 需要防抖的函數
* delay [number] 毫秒执泰,防抖期限值
*/
function debounce(fn,delay){
let timer = null //借助閉包
return function() {
if(timer){
clearTimeout(timer) //進入該分支語句枕磁,說明當前正在一個計時過程中,并且又觸發(fā)了相同事件术吝。所以要取消當前的計時计济,重新開始計時
timer = setTimeout(fn,delay)
}else{
timer = setTimeout(fn,delay) // 進入該分支說明當前并沒有在計時,那么就開始一個計時
}
}
}
簡化:
/*****************************簡化后的分割線 ******************************/
function debounce(fn,delay){
let timer = null //借助閉包
return function() {
if(timer){
clearTimeout(timer)
}
timer = setTimeout(fn,delay) // 簡化寫法
}
}
// 然后是舊代碼
function showTop () {
var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
console.log('滾動條位置:' + scrollTop);
}
window.onscroll = debounce(showTop,1000) // 為了方便觀察效果我們取個大點的間斷值排苍,實際使用根據需要來配置
-
對于短時間內連續(xù)觸發(fā)的事件(上面的滾動事件)沦寂,防抖的含義就是讓某個時間期限(如上面的1000毫秒)內,事件處理函數只執(zhí)行一次纪岁。
節(jié)流(throttle)
- 如果在限定時間段內凑队,不斷觸發(fā)滾動事件(比如某個用戶閑著無聊,按住滾動條不斷的拖來拖去)幔翰,只要不停止觸發(fā)漩氨,理論上就永遠不會輸出當前距離頂部的距離。
但是如果產品同學的期望處理方案是:即使用戶不斷拖動滾動條遗增,也能在某個時間間隔之后給出反饋呢叫惊?(此處暫且不論哪種方案更合適,既然產品爸爸說話了我們就先考慮怎么實現)
方案:
我們可以設計一個類似控制閥門的開關做修,在限定時間內只執(zhí)行一次霍狰,然后不再工作(就像技能冷卻) 等過了這段時間再重新激活抡草。
實現 :
這里借助setTimeout來做一個簡單的實現,加上一個狀態(tài)位valid來表示當前函數是否處于工作狀態(tài):
let valid = true
return function() {
if(!valid){
//休息時間 暫不接客
return false
}
// 工作時間蔗坯,執(zhí)行函數并且在間隔期內把狀態(tài)位設為無效
valid = false
setTimeout(() => {
fn()
valid = true;
}, delay)
}
}
/* 請注意康震,節(jié)流函數并不止上面這種實現方案,
例如可以完全不借助setTimeout,可以把狀態(tài)位換成時間戳宾濒,然后利用時間戳差值是否大于指定間隔時間來做判定。
也可以直接將setTimeout的返回的標記當做判斷條件-判斷當前定時器是否存在绘梦,如果存在表示還在冷卻橘忱,并且在執(zhí)行fn之后消除定時器表示激活卸奉,原理都一樣
*/
// 以下照舊
function showTop () {
var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
console.log('滾動條位置:' + scrollTop);
}
window.onscroll = throttle(showTop,1000)
運行以上代碼的結果是:
- 如果一直拖著滾動條進行滾動,那么會以1s的時間間隔榄棵,持續(xù)輸出當前位置和頂部的距離
其他應用場景舉例
1.搜索框的input事件凝颇,我們就可以使用節(jié)流方案,防止輸入內容不斷的去請求秉继,(間隔一段時間就必須查詢相關內容或者設置輸入間隔大于某個值【500ms】祈噪,就當做用戶已經輸入完成尚辑。
2.頁面resize事件,常用于做頁面的適配杠茬,需要根據最終呈現的頁面情況做dome渲染月褥,此時我們可以使用防抖方案,因為只需要判斷最后一次變化情況瓢喉。
----------------------------原文:https://segmentfault.com/a/1190000018428170-------------------------
個人項目經歷:
vue 的項目有一個需求宁赤,導航有一個釘子效果
- 當釘子開啟的時候,導航就固定定位到頂部栓票,
- 當釘子關閉的時候,滾動條滑動到大于導航高度時 鼠標經過頂部就出現導航條走贪,移開則隱藏。
以上效果正常寫完有個問題坠狡,就是判斷導航高度的時候精度判斷不準確,會導致一些頁面滑動時出現抖動閃爍的問題逃沿,還有因為vue是單頁面應用幻锁,這個效果是全局的边臼,性能消耗大哄尔,還會和其他頁面的原生事件起沖突硼瓣,這時候使用防抖,節(jié)流可以解決這個問題,最終的效果是滾動條滾動停止才觸發(fā)事件堂鲤。