實現原理就是利用定時器,函數第一次執行時設定一個定時器,之後調用時發現已經設定過定時器就清空之前的定時器,並重新設定一個新的定時器,如果存在沒有被清空的定時器,當定時器計時結束後觸發函數執行。 |
防抖函數 debounce 指的是某個函數在某段時間內,無論觸發了多少次回調,都只執行最後一次。
// fn 是需要防抖處理的函數 // wait 是時間間隔 function debounce(fn, wait = 50) { // 通過閉包緩存一個定時器 id let timer = null // 將 debounce 處理結果當作函數返回 // 觸發事件回調時執行這個返回函數 return function(...args) { // this保存給context const context = this // 如果已經設定過定時器就清空上一次的定時器 if (timer) clearTimeout(timer) // 開始設定一個新的定時器,定時器結束後執行傳入的函數 fn timer = setTimeout(() => { fn.apply(context, args) }, wait) } } // DEMO // 執行 debounce 函數返回新函數 const betterFn = debounce(() => console.log('fn 防抖執行了'), 1000) // 停止滑動 1 秒後執行函數 () => console.log('fn 防抖執行了') document.addEventListener('scroll', betterFn)
不過 underscore 中的 debounce 還有第三個參數:immediate 。這個參數是做什麼用的呢?
傳參 immediate 爲 true, debounce會在 wait 時間間隔的開始調用這個函數 。(注:並且在 wait 的時間之內,不會再次調用。)在類似不小心點了提交按鈕兩下而提交了兩次的情況下很有用。
把 true 傳遞給 immediate 參數,會讓 debounce 在 wait 時間開始計算之前就觸發函數(也就是沒有任何延時就觸發函數),而不是過了 wait 時間才觸發函數,而且在 wait 時間內也不會觸發(相當於把 fn 的執行鎖住)。如果不小心點了兩次提交按鈕,第二次提交就會不會執行。
那我們根據 immediate 的值來決定如何執行 fn 。如果是 immediate 的情況下,我們立即執行 fn ,並在 wait 時間內鎖住 fn 的執行, wait 時間之後再觸發,纔會重新執行 fn ,以此類推。
// immediate 表示第一次是否立即執行 function debounce(fn, wait = 50, immediate) { let timer = null return function(...args) { // this保存給context const context = this if (timer) clearTimeout(timer) // immediate 爲 true 表示第一次觸發後執行 // timer 爲空表示首次觸發 if (immediate && !timer) { fn.apply(context, args) } timer = setTimeout(() => { fn.apply(context, args) }, wait) } } // DEMO // 執行 debounce 函數返回新函數 const betterFn = debounce(() => console.log('fn 防抖執行了'), 1000, true) // 第一次觸發 scroll 執行一次 fn,後續只有在停止滑動 1 秒後才執行函數 fn document.addEventListener('scroll', betterFn)
看完了上文的基本版代碼,感覺還是比較輕鬆的,現在來學習下 underscore 是如何實現 debounce 函數的,學習一下優秀的思想,直接上代碼和註釋,本源碼解析依賴於 underscore 1.9.1 版本實現。
// 此處的三個參數上文都有解釋 _.debounce = function(func, wait, immediate) { // timeout 表示定時器 // result 表示 func 執行返回值 var timeout, result; // 定時器計時結束後 // 1、清空計時器,使之不影響下次連續事件的觸發 // 2、觸發執行 func var later = function(context, args) { timeout = null; // if (args) 判斷是爲了過濾立即觸發的 // 關聯在於 _.delay 和 restArguments if (args) result = func.apply(context, args); }; // 將 debounce 處理結果當作函數返回 var debounced = restArguments(function(args) { if (timeout) clearTimeout(timeout); if (immediate) { // 第一次觸發後會設置 timeout, // 根據 timeout 是否爲空可以判斷是否是首次觸發 var callNow = !timeout; timeout = setTimeout(later, wait); if (callNow) result = func.apply(this, args); } else { // 設置定時器 timeout = _.delay(later, wait, this, args); } return result; }); // 新增 手動取消 debounced.cancel = function() { clearTimeout(timeout); timeout = null; }; return debounced; }; // 根據給定的毫秒 wait 延遲執行函數 func _.delay = restArguments(function(func, wait, args) { return setTimeout(function() { return func.apply(null, args); }, wait); });
相比上文的基本版實現,underscore 多了以下幾點功能。
- 1、函數 func 的執行結束後返回結果值 result
- 2、定時器計時結束後清除 timeout ,使之不影響下次連續事件的觸發
- 3、新增了手動取消功能 cancel
- 4、immediate 爲 true 後只會在第一次觸發時執行,頻繁觸發回調結束後不會再執行
本文地址:https://www.linuxprobe.com/anti-shake-function-implementation.html