節流與防抖這兩個概念大家並不陌生,面時高頻題,可是你是否真正瞭解兩者的真正區別?是否能夠在實際開發中知道什麼時候該用防抖?什麼用節流?
閱讀本文你將收穫:
- 清晰認識防抖與節流之間的區別,並能夠正確的應用與實際開發中
- 多樣的程式碼實現
- 閉包的特性的應用
什麼是防抖
debounce(fn, threshhold)
技術最終都要服務於社會,任何脫離業務(社會)實際的都是耍流氓,防抖當然也不例外,實際生活中對於拍照(人工防抖,不是智慧那種:joy:),如果你在自拍,你肯定不會在鏡頭沒穩定之前,按下快門吧(PS:如果你想要這種模糊效果當我沒說:no_mouth:),也就是鏡頭不穩(手抖)你不會按下快門,如果你感覺穩了,才會按下快門,類似:
- 防抖函式
debounce
的功能就相當於幫你判斷什麼時候該按下快門 fn
相當於快門threshhold
(閾值)就相當於人體感知穩定的需要經歷的時間閾值- 只有穩定之後才會按下快門即執行fn,也就是說一旦間隔
threshhold
有一次抖動都會重新判斷穩定 - 如果
threshhold
間隔內一直穩定不下來,第一次觸發threshhold
ms之後fn不會被執行,同理一直不穩定,fn永遠不會被執行(假如死迴圈)
// fn => 2
function debounce(fn, threshhold){ // 1
if(!fn instanceof Function) {
throw new TypeError('Expected a function')
}
let timer = null;
return function () {
clearTimeout(timer); // 3
timer = setTimeout(() => {
fn.apply(this) // 4
},threshhold)
};
}
複製程式碼
程式碼淺析:debounce
就相當於幫你判斷是什麼時候該按下快門,要執行的fn相當於快門,人體的感知穩定時間閾值為threshhold
,如果連續兩次呼叫(對應拍照抖動)小於threshhold
,那麼肯定要重新設定穩定間隔的起始點也就是重置clearTimeout(timer)
,當然如果兩次間隔超過threshhold
,重置已經無法影響了已經發生的呼叫了,最後定時器執行fn.apply(this)
就是手終於不抖可以按下快門啦:joy:
什麼是節流
throttle(fn, threshhold)
實際生活中,節流這一概念其實生活中有很多例子,比如這快過年了,火車站考慮到大家的安全,對進站進行節流(官方應該叫限流,其實表達都是同一個意思)因為單位時間內車站的接待(容納)人數是有限的,還有大家更加熟悉的例子,王者榮耀或者英雄聯盟這類moba遊戲,都有攻速上限(攻速2.5),換句話說哦假設程式設定了英雄一秒最多A五下,那麼即使你手速再快,1s內也A不出第六下,通過以上例子我們可以得出:
threshhold
間隔內函式fn
無論觸發多少次,第一次觸發到threshhold
ms後都是隻執行一次- 第二次觸發距離第一次時間超過
threshhold
,則第二次會立即執行
根據這兩點,有兩種實現方式
第一種
function throttle(fn, threshhold) {
if(!fn instanceof Function) {
throw new TypeError('Expected a function')
}
let limited = false; // 節流閥標誌位
let start = Date.now();
threshhold = threshhold || 500
return function (...args) {
let current = Date.now();
limited = limited && current- start < threshhold
if(!limited) {
fn.apply(this,args);
limited = true;
start = Date.now();
}
}
}
複製程式碼
程式碼淺析:通過limited
節流閥標誌位模擬當前是否需要節流(限流),第一次預設false
即首次不限流(車站為空的:joy:),限流之後(limited = true
)且只有兩次時間間隔(current- start
)超過threshhold
,才會除去限制,呼叫fn即車站讓旅客進站進入新的週期重置開始時間start
第二種
function throttle2(fun, threshhold) {
if(!fun instanceof Function) {
throw new TypeError('Expected a function')
}
let limited = false; // 節流閥標誌位
let timer = null;
let start = Date.now();
threshhold = threshhold || 500
return function (...args) {
let current = Date.now();
limited = limited && current- start < threshhold
if (limited) {
clearTimeout(timer)
timer = setTimeout(() => {
limited = true
start = Date.now()
fun.apply(this, args)
}, threshhold)
}else {
limited = true
start = Date.now();
fun.apply(this,args)
}
}
}
複製程式碼
程式碼淺析:第二種使用了setTimeout
定時器的方式,多加了如果最後一次觸發距離上一次呼叫fn
小於threshhold
則這次設定的定時器回撥將會在下一個threshhold
週期內執行,所以這種方式觸發多次fn
總共會執行兩次
,只是第二次會在下一個threshhold
週期內執行
節流兩種方式對比
- 第一種,一個
threshhold
間隔內多次促發,fn
只會被執行一次,最後一次並不會進入下一個週期執行,比如連續1秒內平A了5次超過限度(節流)5次,第六次並不會說下一秒自動平A,而是直接捨去- 第二種,一個
threshhold
間隔內多次促發,fn
總共會執行兩次,注意第二次會進入下一個threshhold
週期執行
兩者比較
相同點:
- 其實本質上都是為了節省程式的效能(防止高頻函式呼叫)
- 藉助了閉包的特性來快取變數(狀態)
- 都可以使用setTimeout實現
區別:
- 使用防抖,可能n個
threshhold
時間間隔之後fn
也沒執行,但是使用節流觸發的threshhold
間隔內有且只執行一次 - 同樣
threshhold
間隔內連續觸發,防抖只執行一次,而節流會執行兩次,只是在不同的threshhold
週期內 - 側重點不同,防抖側重於穩定只能執行一次,而節流強調限週期內次數,即執行頻率,不限制所有時間內的總次數
應用場景
防抖:
- 一些表單元素的校驗,如手機號,郵箱,使用者名稱等
- 部分搜尋功能的聯想結果實現
節流:
- 一些滑鼠的跟隨動畫實現
- scroll,resize, touchmove, mousemove等極易持續性促發事件的相關動畫問題,降低頻率
總結
無論什麼技術都有他擅長的地方,技術與實現的優與劣不能單從技術方面去考量,這樣是沒有意義的,如對於節流函式的兩種方式,他們都有適合的場景,比如你的產品需要類似遊戲內限制攻速的,顯然節流的第一種方案更合適,元芳你怎麼看?:laughing:(ps:github原始碼地址含單測)