前端中的防抖和節流具體實現
阿新 • • 發佈:2022-06-05
哈嘍 大家好!最近把面試總經常問到的防抖和節流知識整體總結一下,下面就分享給大家:
先介紹一下什麼是防抖和節流,有兩個問題可以有助於我們理解抽象的概念:為什麼需要防抖和節流?哪些場景有都應用?
我們可以從這兩個問題來介紹防抖和節流,從生活中舉例子來理解這兩個抽象概念
防抖執行的函式不會立即執行,例如:使用者在輸入框中頻繁輸入內容,搜尋或者提交資訊;頻繁的點選某一個按鈕,觸發某個事件;監聽瀏覽器的滾動事件,完成某些特定操作等等。
節流執行的函式會按照一定頻率或者週期去執行,例如:使用者頻繁點選按鈕操作、監聽頁面的滾動事件、滑鼠移動事件等等。
防抖的原理是什麼?
當某一個事件觸發時,相應的函式不會立即
節流的原理又是什麼?
當事件觸發時,會執行這個事件的響應函式,如果這個事件會被頻繁的觸發,那麼節流函式會按照一定的頻率來執行,不管在這個中間有多少次觸發事件,執行函式的頻率總是固定不變的。(在某一段時間觸發的事件是非常多的,但是我們只在某段時間只觸發一次)
防抖函式的簡單實現:
HTML程式碼:
<input type="text" class="text" /> <button class="btn">取消</button>
CSS程式碼:
input { width: 300px; height: 40px; line-height: 40px; font-size: 20px; } button { width: 100px; height: 45px; font-size: 20px; }
JS程式碼:
/** * 沒有引數的節流函式 * @param {*} fn * @param {*} delay * @returns*/ function debounce_A(fn, delay) { let timer = null; // 真正執行的函式 const _debounce = function () { if (timer) { // 取消上一次的定時器 clearTimeout(timer); } timer = setTimeout(function () { fn(); }, delay); }; return _debounce; } /** * 帶有引數的節流函式 * @param {*} fn * @param {*} delay * @returns */ function debounce_B(fn, delay) { let timer = null; // 真正執行的函式 const _debounce = function (...args) { if (timer) { // 取消上一次的定時器 clearTimeout(timer); } timer = setTimeout(() => { // 外部傳入的真正要執行函式 fn.apply(this, args); }, delay); }; return _debounce; } /** * 第一次是否立即會執行節流函式 和 取消執行 * @param {*} fn * @param {*} delay * @param {*} immediate */ function debounce_C(fn, delay, immediate = false) { let timer = null; let isInvoke = false; // 真正執行的函式 const _debounce = function (...args) { if (timer) { // 取消上一次的定時器 clearTimeout(timer); } if (immediate && !isInvoke) { // 第一次要執行的函式 fn.apply(this, args); isInvoke = true; } else { timer = setTimeout(() => { // 外部傳入的真正要執行函式 fn.apply(this, args); // 間隔了一會兒,又想起來要輸入,第一次需要被執行 isInvoke = false; }, delay); } }; // 取消功能 _debounce.cancel = function () { if (timer) { clearTimeout(timer) } timer = null isInvoke = false } return _debounce; }
分別對三個功能簡單測試一下:
var btn = document.querySelector(".btn"); var text = document.querySelector(".text"); var count = 0; const inputChange = function (event) { console.log(`第${++count}次網路請求`, this, event); }; // text.oninput = debounce_A(inputChange, 2000); // text.oninput = debounce_B(inputChange, 2000); const debounceChange = debounce_C(inputChange, 2000, true); text.oninput = debounceChange; btn.addEventListener("click", function () { console.log(`已經取消第${++count}次網路請求`, this, event); debounceChange.cancel(); });
節流函式的簡單實現:
/** * 基本實現 * @param {*} fn * @param {*} interval * @returns */ function throttle_A(fn, interval) { // 記錄上一次開始的時間 let lastTime = 0; const _throttle = function () { // 2.獲取當前函式觸發的時間 let nowTime = new Date().getTime(); // 3.真正需要執行的時間,當前時間減去上一次執行過的時間,再用延遲時間減去這個時間,就是這一次需要真正執行的時間 let remainTime = interval - (nowTime - lastTime); if (remainTime <= 0) { fn(); // 4.保留上次觸發的時間 lastTime = nowTime; } }; return _throttle; }
/** * 解決第一次執行函式後,如果再次觸發就會立即執行,應該是第一次執行函式後,再次觸發時,需要判斷是否超過延時時間 * leading :控制第一次要不要去執行的功能 * @param {*} fn * @param {*} interval * @param {*} options * @returns */ function throttle_B( fn, interval, options = { leading: true, trailing: false } ) { // 記錄上一次開始的時間 let lastTime = 0; let timer = null; let { leading, trailing } = options; const _throttle = function () { // 2.獲取當前函式觸發的時間 let nowTime = new Date().getTime(); // 2.1 如果是第一次執行才會去觸發, 把第一次/上一次執行的執行時間記錄 if (!lastTime && !leading) { lastTime = nowTime; } // 3.真正需要執行的時間,當前時間減去上一次執行過的時間,再用延遲時間減去這個時間,就是這一次需要真正執行的時間 let remainTime = interval - (nowTime - lastTime); if (remainTime <= 0) { fn(); // 4.保留上次觸發的時間 lastTime = nowTime; } }; return _throttle; }
/** * 是否延遲執行 * @param {*} fn * @param {*} interval 是否需要立即執行 * @param {*} options * @returns */ function throttle_C( fn, interval, options = { leading: true, trailing: false } ) { // 記錄上一次開始的時間 let lastTime = 0; let timer = null; // leading:開始是否觸發 , trailing:最後一次是否會執行 let { leading, trailing } = options; const _throttle = function (...args) { // 2.獲取當前函式觸發的時間 let nowTime = new Date().getTime(); // 2.1 如果是第一次執行才會去觸發, 把第一次/上一次執行的執行時間記錄 if (!lastTime && !leading) { lastTime = nowTime; } // 3.真正需要執行的時間,當前時間減去上一次執行過的時間,再用延遲時間減去這個時間,就是這一次需要真正執行的時間 let remainTime = interval - (nowTime - lastTime); if (remainTime <= 0) { if (timer) { clearTimeout(timer); timer = null; } // 3.1 真正執行觸發的函式 fn.apply(this, args); // 4.保留上次觸發的時間 lastTime = nowTime; return; } // 5. 判斷最後一次是否會執行 if (trailing && !timer) { timer = setTimeout(() => { timer = null; // 控制最後一次執行的時間 lastTime = leading ? new Date().getTime() : 0; fn.apply(this, args); }, remainTime); } }; return _throttle; }