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

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

一、what?

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

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

圖1 debounce、throttle運行圖瀏覽器

如圖所示,其中delay=4,因爲紅色操做序列與綠色操做序列之間的時間間隔小於delay,因此這兩個序列被視爲一個連續操做行爲。服務器

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

結合運行圖,能夠更好的理解debounce、throttle的做用。閉包

二、why?

經常使用情景:app

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

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

三、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的觸發控制在必定的範圍內。

四、思考

圖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還有毛線用?

相關文章
相關標籤/搜索