一文讀懂防抖和截流


本篇文章梗概:

什么是防抖和節流?
他們有什么區別?
分別如何實現?

 

什么是防抖和節流?
防抖和節流,都是開發過程中防止函數多次調用的方式。我現在寫的主要是前端開發中的防抖和節流的介紹。

 

什么是防抖?
防抖,顧名思義,防止抖動,以免把一次事件誤認為多次,敲鍵盤就是一個每天都會接觸到的防抖操作。

想要了解一個概念,必先了解概念所應用的場景。在 JS 這個世界中,有哪些防抖的場景呢?

1. 登錄、發短信等按鈕避免用戶點擊太快,以致於發送了多次請求,需要防抖。
2. 調整瀏覽器窗口大小時,resize 次數過於頻繁,造成計算過多,此時需要一次到位,就用到了防抖。
3. 文本編輯器實時保存,當無任何更改操作一秒后進行保存。

 

什么是節流?
節流,顧名思義,控制水的流量。控制事件發生的頻率,如控制為1s發生一次,甚至1分鍾發生一次。與服務端(server)及網關(gateway)控制的限流 (Rate Limit) 類似。

scroll 事件,每隔一秒計算一次位置信息等
瀏覽器播放事件,每個一秒計算一次進度信息等
input 框實時搜索並發送請求展示下拉列表,沒隔一秒發送一次請求 (也可做防抖)

 

防抖和節流的區別
防抖:防止抖動,單位時間內事件觸發會被重置,避免事件被誤傷觸發多次。代碼實現重在清零 clearTimeout。
節流:控制流量,單位時間內事件只能觸發一次,如果服務器端的限流即 Rate Limit。代碼實現重在開鎖關鎖 timer=timeout; timer=null

 

防抖和節流的代碼實現

防抖的實現

/**
* 防抖(debounce)防止抖動:觸發高頻事件后 n 秒內函數只會執行一次,如果 n 秒內高頻事件再次被觸發,則重新計算時間。
* @param {*} func 調用用的函數,function
* @param {*} wait 等待的時間,單位ms
* @param {*} immediate 當immediate為true時,第一次調用該函數的時候,就調用func函數;false表示超時之后再調用
*/
export function debounce(func, wait, immediate) {
let timer;
// 通過閉包緩存一個定時器 id
return function () {
// 將 debounce 處理結果當作函數返回
// 觸發事件回調時執行這個返回函數
let context = this;
// 把上下文的this對象保存下來,因為下面的apply要使用
let args = arguments;
console.log(context,"90909090")
// ...args:使用es6的rest運算符,把逗號隔開的值序列組合成一個數組:如test(1,2,3,4) ==> args:[1,2,3,4]
// 符合apply(obj,[])
if (timer) clearTimeout(timer);
//關鍵點 防抖重在清零
// 如果已經設定過定時器就清空上一次的定時器,clearTimeout取消延遲執行的代碼塊
if (immediate) {
// 如果immediate為true,那么立馬調用該函數
var callNow = !timer;
timer = setTimeout(() => {
timer = null;
}, wait);
if (callNow) func.apply(context, args);
// 過了設定的時間,才執行傳過來的函數
} else {
// 開始設定一個新的定時器,定時器結束后執行傳入的函數 fn
timer = setTimeout(function () {
func.apply(context, args);
}, wait);
}
};
}

 

節流的實現

//這個是在上一個函數上的改進,加強版節流函數 throttle
//如下,新增邏輯在於當前觸發時間和上次觸發的時間差小於時間間隔時,設立一個新的定時器,相當於把 debounce 代碼放在了小於時間間隔部分。
export function throttle(fn, wait) {
let timer = null;
let prev = new Date();
return function () {
let nowTime = new Date();
// 獲取當前時間,轉換成時間戳,單位毫秒
let context = this;
clearTimeout(timer);
// ------ 新增部分 start ------
// 判斷上次觸發的時間和本次觸發的時間差是否小於時間間隔
// 如果小於,則為本次觸發操作設立一個新的定時器
// 定時器時間結束后執行函數 fn
if (nowTime - prev > wait) {
fn.apply(context, arguments);
prev = new Date();
// ------ 新增部分 end ------
} else {
timer = setTimeout(() => {
fn.apply(context, arguments);
}, wait);
}
};
}

 

延伸:
es6 rest argument
es6 引入了rest參數(形式:...變量名),用於獲取函數的多余參數,這樣就不需要使用arguments對象了。rest參數搭配的變量是一個數組,該變量將多余的參數放入數組中。

var fun = (...item)=>{
console.log(item)
}
fun(11,22,33,44,55,66,77,88,99)

 

apply方法
apply:方法能劫持另外一個對象的方法,繼承另外一個對象的屬性.

Function.apply(obj,args)方法能接收兩個參數
obj:這個對象將代替Function類里this對象
args:這個是數組,它將作為參數傳給Function(args-->arguments)

用法:將數組作為函數參數
例子:Math.max后面可以接任意個參數,最后返回所有參數中的最大值。
一般這樣做,需要取數組里面每個值,畢竟很多傳參是接受對象的

function getMax(arr){
var arrLen=arr.length;
for(var i=0,ret=arr[0];i<arrLen;i++){
ret=Math.max(ret,arr[i]);
}
return ret;
}


用apply就簡單很多

function getMax2(arr){
return Math.max.apply(null,arr);
}

 

這樣看來`...args`,使用es6的rest運算符,把逗號隔開的值序列組合成一個數組:如test(1,2,3,4) ==> args:[1,2,3,4],
`apply`把數組轉成對象。這里args是繼承的傳入函數的參數。

防抖代碼,關鍵就是,對象轉換兩遍,clearTimeout取消延遲執行的代碼塊,清零。

其實不管防抖和截流還有很多其他方法,都可以用loadash類庫去實現,不用自己苦哈哈的寫。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM