1. 程式人生 > >性能提速:debounce(防抖)、throttle(節流/限頻)

性能提速:debounce(防抖)、throttle(節流/限頻)

mat 控制臺 nim 分享圖片 ons 布局 settime why 交互

debounce與throttle是用戶交互處理中常用到的性能提速方案,debounce用來實現防抖動,throttle用來實現節流(限頻)。那麽這兩個方法到底是什麽(what)?為何要用(why-解決什麽問題)?具體的實現原理,以及函數運行過程是怎樣的呢(how)?

1、what?

連續操作:兩個操作之間的時間間隔小於設定的閥值,這樣子的一連串操作視為連續操作。

debounce(防抖):一個連續操作中的處理,只觸發一次,從而實現防抖動。
throttle:一個連續操作中的處理,按照閥值時間間隔進行觸發,從而實現節流。

技術分享圖片

圖1 debounce、throttle運行圖

如圖所示,其中delay=4,由於紅色操作序列與綠色操作序列之間的時間間隔小於delay

,所以這兩個序列被視為一個連續操作行為。

  • debounceTail:執行操作在連續操作完成之後,觸發;
  • debounceStart:執行操作在連續操作完成之前,觸發;
  • throttle:在一個連續操作行為中,每間隔delay的時間觸發1次。

結合運行圖,可以更好的理解debounce、throttle的作用。

2、why?

常用情景:

  • a、scroll事件:當頁面發生滾動時,scroll事件會被頻繁的觸發,1s觸發可高達上百次。在scroll事件中,如果有復雜的操作(特別是影響布局的操作),將會大大影響性能,甚至導致瀏覽器崩潰。所以,對其進行防抖、限頻很重要。
  • b、click事件:用戶進行click事件時,有可能連續觸發點擊(用戶本意並非雙擊)。該操作有可能是不小心多次連續點擊,也可能是頁面狀況不好的情況下,期待盡快得到反饋的有意行為;但這樣的操作,反而會加劇性能問題,因此也有必要考慮防抖、限頻。
  • c、input事件:如sug等需要通過ajax及時獲得數據的情況,需要進行限頻,防止頻繁的請求發生,減少服務器壓力的同時,提高頁面響應性能。
  • d、touchmove事件:同scroll事件類似。

還有許多其他業務場景會出現頻繁操作的情況,不一一列舉。debounce可用於:防止用戶的多次click提交;scroll下拉刷新時,同一位置多次請求數據等。throttle可應用於,scroll設置定位等的頻繁位置計算;拖拽的頻繁位置計算等。

3、how?

怎樣實現?其代碼實現如下:

// 防抖 且首次執行
// 采用原理:第一操作觸發,連續操作時,最後一次操作打開任務開關(並非執行任務),任務將在下一次操作時觸發)
function debounceStart(fn, delay, ctx) {
    let immediate = true 
    let movement = null
    return function() {
        let args = arguments
        
        // 開關打開時,執行任務
        if (immediate) {
            fn.apply(ctx, args)
            immediate = false
        }
        // 清空上一次操作
        clearTimeout(movement)
        
        // 任務開關打開
        movement = setTimeout(function() {
            immediate = true
        }, delay)
    }
}

// 防抖 尾部執行
// 采用原理:連續操作時,上次設置的setTimeout被clear掉
function debounceTail(fn, delay, ctx) {
    let movement = null
    return function() {
        let args = arguments
        
        // 清空上一次操作
        clearTimeout(movement)
        
        // delay時間之後,任務執行
        movement = setTimeout(function() {
            fn.apply(ctx, args)
        }, delay)
    }
}

// 限頻,每delay的時間執行一次 
function throttle(fn, delay, ctx) {
    let isAvail = true
    return function() {
        let args = arguments
        
        // 開關打開時,執行任務
        if (isAvail) {
            fn.apply(ctx, args)
            isAvail = false
            
            // delay時間之後,任務開關打開
            setTimeout(function() {
                isAvail = true
            }, delay)
        }
    }
}
    
// 調用
btn.onclick = debounceStart(function(event) {
    console.log('100ms')
}, 100, this) 
window.onscroll = throttle(function(event) {
    console.log('100ms')
}, 100, this) 

如上代碼,使用了閉包,將isAvail等父級變量存儲在了內存當中,實現狀態切換。同時,通過apply將任務函數的上下文ctx(在類、對象內操作時,其作用更明顯);參數arguments(如調用中的event),存入最終的任務執行函數當中。通過timer的clear和set來控制任務的觸發,同時需留意任務執行與任務開關打開的區別。任務執行是timer到達,就將觸發任務;任務開關打開是timer到達時,只將狀態變更,需要用戶的再一次操作,才能實施真正的任務觸發。

通過控制臺可以看到,不進行限頻時,scroll在1s內可以觸發高達上100次,增加了限頻之後,就將scroll的觸發控制在一定的範圍內。

4、思考

技術分享圖片

圖2 throttle運行標示圖

在實際的使用場景當中,我們會發現,用戶最後一次操作並沒有後續的處理,也就是最後一次操作的狀態將丟失。在某些應用場景當中,可能造成狀態處理不準確。如通過scroll事件判斷是否到達頁面底部,如果到達,則提示用戶。使用throttle方法進行節流,在到達底部之前,小於delay的時間間隔內,觸發了一次位置判斷操作;下一次觸發將在delay時間之後,但在那之前,scroll事件已經結束了,所以無法獲取最後scroll到底部的位置,也就不會觸發提示。

如何優化呢?
可以結合debounceTail的功能,其可以實現最後一次操作的捕捉,如圖所示:

技術分享圖片

圖3,throttle加強運行圖

其代碼如下:

// 限頻,每delay的時間執行一次 缺點:如果可能無法捕獲最後一次結果
function throttle(fn, delay, ctx) {
    let isAvail = true
    let count = false
    let movement = null
    return function() {
        count = true
        let args = arguments
        if (isAvail) {
            fn.apply(ctx, args)
            isAvail = false
            count = false
            setTimeout(function() {
                isAvail = true
            }, delay)
        }
        if (count) {
            clearTimeout(movement)
            movement = setTimeout(function() {
                fn.apply(ctx, args)
            }, 2 * delay)
        }
    }
}

增加movement來記錄和清除最終操作狀態;用count來避免與限頻的重合;如此便實現了捕獲最終操作狀態的限頻操作。

tips:其中大量使用setTimeout()的操作,在高級瀏覽器中,可以使用requestAnimationFrame來替代setTimeout操作,從而提高性能。requestAnimationFrame的原理、優勢及低版本的兼容,可以查閱張鑫旭的博客,寫得很詳細:CSS3動畫那麽強,requestAnimationFrame還有毛線用?

性能提速:debounce(防抖)、throttle(節流/限頻)