防抖和節流都是爲了避免窗口的resize、scroll、輸入框內容校驗等事件處理函數被頻繁調用,因此採用防抖和節流來限制事件調用的頻率
防抖
原理介紹
防抖是指當持續觸發事件時,一定時間段內沒有再次觸發事件,事件處理函數纔會執行一次,如果設定的時間段內又一次觸發了事件,就重新開始計時。
防抖在很多場景中都有應用:
- 輸入框中頻繁的輸入 內容時,間隔一段時間來檢驗用戶輸入的內容
- 頻繁點擊按鈕來觸發某個事件時
- 監聽瀏覽器的滾動事件
- 窗口的resize事件
舉個例子,比如淘寶購物時詢問客服,在交流過程中,客服會等待用戶一分鐘的時間(假設),如果在一分鐘內,用戶還會發消息(事件觸發),那麼客服就會回消息,然後繼續等待一分鐘,看看用戶是否還會發消息;如果等待時間超過一分鐘了,那麼就斷開和用戶的連接(執行響應函數)
在上圖中,事件觸發並不是立即執行響應函數,而是等待一段時間後纔會觸發響應函數;而且當是事件觸發多次時,等待時間會持續推遲到最後一個事件觸發,然後等待一段時間後執行響應函數。因此都是以最後一次事件觸發開始計時。
代碼實現及優化
自定義實現
實現思路:
- 當觸發一個函數時,不會立即執行該函數,而是等待一段時間後再執行(通過定時器來延遲函數的執行);
- 如果在等待時間段內,又重新觸發函數,那就取消上一次的函數執行(取消定時器);
- 如果在延遲時間內,沒有重新觸發函數,那麼這個函數將正常執行
//防抖函數的實現(fun爲需要處理的函數,wait爲延遲時間)
//默認情況下,fun函數的this是指向window的,但若fun函數需要用到返回函數中的arguments時,就需要改變this指針了
function debounce(fun, wait){
let timer = null;
return function(){
if(timer) {clearTimeout(timer)};
//獲取this和arguments
let context = this;
let args = arguments;
timer = setTimeout(function(){
//獲取對應節點對象的this和arguments
fun.apply(context, args);
},wait)
}
}
//ES6新寫法,箭頭函數沒有this,會向外查找this
function debounce(fun, wait){
let timer = null;
return ()=>{
if(timer) {clearTimeout(timer)};
timer = setTimeout(function(){
//獲取對應節點對象的this和arguments
fun.apply(this, arguments);
},wait)
}
}
優化立即執行
若希望當第一次事件觸發時就立即執行響應事件函數,而不是等待一段時間,後續事件觸發時纔等待。可以使用flag來標記是否是第一次執行,若爲true,就按照第一次來執行,爲false就按照防抖來執行
//優化,flag是用戶上傳來判斷第一次是否立即執行
function debounce(fun,wait, flag){
let timer = null;
flag = flag || false;
let handleFun = function(){
if(timer){clearTimeout(timer);}
let context = this;
let args = arguments;
if(flag){
//設置一個變量來判斷是否立即執行
let isExe = false;
if(!timer){
fun.apply(context, args);
isExe = true;
}
timer = setTimeout(function(){
timer = null;
if(!isExe){
fun.apply(context, args);
}
},wait);
}else{
timer = setTimeout(function(){
fun.apply(context, args);
}.wait)
}
}
return handleFun;
}
優化返回值
若想讓fun函數執行後有返回值,但是fun函數是發生在setTimeout函數中(異步執行的),所以通過return無法獲取返回值
異步操作可以通過
promise以及回調函數
來獲取返回值
//promise函數實現返回值
function debounce(fun, wait){
let timer = null;
return function(){
return new Promise((resolve, reject)=>{
if(timer) {clearTimeout(timer);}
let context = this;
let args = arguments;
timer = setTimeout(function(){
resolve(fun.apply(context, args));
},wait);
})
}
}
//回調函數實現
function debounce(fun, wait, result){
let timer = null;
result = result || null; //通過result將結果回調出去
return function(){
if(timer){clearTimeout(timer);}
let context = this;
let args = arguments;
timer = setTimeout(function(){
let res = fun.apply(context, args);
if(result){
result(res);
}
},wait);
}
}
節流
原理介紹
當持續觸發事件時,保證一定時間內只調用一次事件處理函數。
比如飛機大戰遊戲,當我們按下空格鍵時會發射一顆子彈,但是當按下空格鍵頻率很快時,子彈也會保持一定的頻率來發射,而不是按一次就發射一顆子彈。按下空格的操作在一定時間段內觸發了10次,但是響應函數就觸發了一次
應用場景:
- 監聽頁面的滾動事件
- 鼠標移動事件
- 頻繁點擊按鈕
- 遊戲中的一些設計
上圖中,等待時間是固定的,不管在等待時間段內觸發幾次事件,響應函數只觸發一次
代碼實現及優化
自定義實現
實現思路:採用時間戳的方式來實現
- 用previous來記錄上一次執行的時間
- 每次準備執行前,獲取當前的時間now
- 函數執行後,將now賦值給previous
function throttle(fun, wait){
let previous = 0;
return function(){
let context = this;
let args = arguments;
let now = new Date().getTime();//獲取時間戳
if(now - previous > wait){
fun.apply(context, args);
previous = now;
}
}
}
//ES6新寫法
function throttle(fun, wait){
let previous = 0;
return ()=>{
let now = new Date().getTime();//獲取時間戳
if(now - previous > wait){
fun.apply(this, arguments);
previous = now;
}
}
}
優化最後執行
通過上圖可以看出,默認情況下,節流函數的最後一次不會執行。若希望最後一次能夠執行,可以這樣實現:
//優化最後執行
function throttle(fun,wait){
let previous = 0;
let timer = null;
return function(){
let context = this;
let args = arguments;
let now = new Date().getTime();
if(now - previous > wait){
if(timer){
clearTimeout(timer);
timer = null;
}
fun.apply(context, args);
previous = now;
}else if(timer === null){ //只是最後一次
timer = setTimeout(function(){
timer = null;
fun.apply(context,args);
},wait)
}
}
}
兩者區別
防抖是將幾次操作合併爲一次進行;節流是在一定時間內只執行一次函數。
總而言之,防抖只是在最後一次事件後才觸發函數;節流是保證在規定時間內執行一次事件處理函數
參考鏈接: https://mp.weixin.qq.com/s/qyeRecCBBwa-Zf_V-KIRxA