1. 程式人生 > 其它 >前端中的防抖和節流具體實現

前端中的防抖和節流具體實現

哈嘍 大家好!最近把面試總經常問到的防抖和節流知識整體總結一下,下面就分享給大家:

先介紹一下什麼是防抖和節流,有兩個問題可以有助於我們理解抽象的概念:為什麼需要防抖和節流?哪些場景有都應用?

我們可以從這兩個問題來介紹防抖和節流,從生活中舉例子來理解這兩個抽象概念

防抖執行的函式不會立即執行,例如:使用者在輸入框中頻繁輸入內容,搜尋或者提交資訊;頻繁的點選某一個按鈕,觸發某個事件;監聽瀏覽器的滾動事件,完成某些特定操作等等。

節流執行的函式會按照一定頻率或者週期去執行,例如:使用者頻繁點選按鈕操作、監聽頁面的滾動事件、滑鼠移動事件等等。

防抖的原理是什麼?

當某一個事件觸發時,相應的函式不會立即

觸發,而是會等待一定時間,再去觸發,例如:當用戶在搜尋框輸入時需要請求請求伺服器,如果每輸入一個文字去請求伺服器一定會降低伺服器效能、增加伺服器處理的壓力,因為請求請求需要傳送發給伺服器很多次,只有等待了一段時間後用戶沒有再去觸發事件,才會真正的執行響應函式;基於這個原理,我可以分步驟簡單來實現一下防抖函式。(當然這裡使用第三方庫很多,例如:loadsh、underscore)

節流的原理又是什麼?

當事件觸發時,會執行這個事件的響應函式,如果這個事件會被頻繁的觸發,那麼節流函式會按照一定的頻率來執行,不管在這個中間有多少次觸發事件,執行函式的頻率總是固定不變的。(在某一段時間觸發的事件是非常多的,但是我們只在某段時間只觸發一次)

防抖函式的簡單實現:

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;
}