跳到主要內容

JavaScript 優化使用者輸入事件效能技巧: debounce & throttle

debounce 跟 throttle 不是什麼新奇的技巧
但是對部分想理解這個概念的前端開發者來說,障礙可能是如何下搜尋關鍵字
畢竟以字面來說,去抖動跟節流沒很直接從字面上聯想到適用場景
但要如何在使用者持續輸入的過程中
還要盡量避免多餘的同樣函式呼叫在現代卻是越來越重要的開發優化目標

舉個常見的例子
前端盡可能的即時儲存使用者輸入的內容(auto save)已經是常見需求
這樣一來可以避免使用者端已經文章寫了一大篇
但卻因為手滑、錯誤操作而在最後失去所有內容
不過若你要使用者每敲一次鍵盤就送一次存檔要求
會造成敲一篇文章就可能有數萬個連線要求
在前端處理耗費或是網路頻寬占用上都很沒效率
在這個狀況我們很合適用 debounce 的技巧處理
英文的命名原因查資料是以模仿電器開關為想法,合併多個訊號為一個訊號

人類編輯文章的過程中不是機器
不會在極短的反應時間(如100微秒內)持續產出有意義的文字內容到結束
而且丟失有意義內容前的內容並不會給我們帶來副作用
像是存了apple這個詞,我們不知道使用者編輯過程中曾經有過 appl 的文字也沒關係
所以我們可以設定一個合理反應時間的計時器
合併反應時間內持續的輸入,並重設計時器安排執行合併後的結果

把這個概念包裝成函式如下:

function debouncedFn(delay, fn) {
    let timerId = null;
    return function () {
        let context = this, args = arguments;

        if (timerId) {
            clearTimeout(timerId);
        }
        timerId = setTimeout(function () {
            fn.apply(context, args);
            timerId = null;
        }, delay);
    };
}


合併反應時間內持續的輸入=clearTimeout原本的內容,並重新送出setTimeout處理
使用範例 eventTarget.addEventListener('keydown', debouncedFn(100, sampleFunction));

另一種類似的處理法則是限制函式的最短執行間隔
throttle 字面上的節流的意思比 debounce 好理解

function throttledFn(delay, fn) {
    let lastCall = 0;
    return function () {
        let now = new Date().getTime();
        if (now - lastCall < delay) {
            return;
        }
        lastCall = now;
        return fn.apply(this, arguments);
    };
}


舉例記錄使用者的滑鼠移動軌跡,並做出模擬的重播效果
我們不需要詳實紀錄每1微秒間的滑鼠游標位置差距
這些差距小到人類難以察覺
若改成最短每50微秒紀錄一次,並讓動畫滑順化
這樣使用者也會覺得這段重播效果跟他之前的操作沒顯著差異

前面兩種概念從說明上就可以清楚知道想法的差異
優化事件的核心概念就是怎麼找出可以被忽略的執行
再根據哪種想法更容易套用在實作以及維護上做抉擇

留言