本篇文章梗概:
什么是防抖和節流?
他們有什么區別?
分別如何實現?
什么是防抖和節流?
防抖和節流,都是開發過程中防止函數多次調用的方式。我現在寫的主要是前端開發中的防抖和節流的介紹。
什么是防抖?
防抖,顧名思義,防止抖動,以免把一次事件誤認為多次,敲鍵盤就是一個每天都會接觸到的防抖操作。
想要了解一個概念,必先了解概念所應用的場景。在 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類庫去實現,不用自己苦哈哈的寫。