淺談throttle以及debounce的原理和實現

淺談throttle以及debounce的原理和實現


背景

平常開發中,咱們常常會遇到一些須要節流調用,或者壓縮調用次數的狀況,例如以前我在完成一個需求的時候,就遇到了由於後端併發問題,致使收到多條socket信息從而致使函數被重複調用的狀況,當時的作法是經過setTimeout對函數的調用進行註冊,遇到屢次調用的時候,清空前一次的調用,之後一次爲準.後來在閱讀underscore源碼的時候,發現這種作法與debounce以及throttle的實現不謀而合.於是簡單記錄一下.jquery

throttle與debounce

throttledebounce在用於控制函數的屢次調用的時候,很是的有效.throttle函數可以控制目標函數在必定的時間內最多隻會調用一次.而debounce函數,則能夠壓縮調用的次數,把屢次函數調用壓縮成只調用一次(屢次的函數調用之間的間隔不能超過規定的時間間隔).
這樣文字描述起來可能比較難理解,不過沒關係,當初我在看underscore源碼的時候,也是很是難以理解這兩個函數的需求,感受好像都差很少同樣.並且underscore1.1.3版本中,採用了相同的底層實現,只是接口傳入的參數不一樣而已.於是咱們這裏採用一個坐電梯的例子來講明.後端

debounce電梯

假設你正在準備乘坐電梯,而且電梯門準備關上而後上升的時候,你的同事來了,出於禮貌,咱們須要中止電梯的關閉,讓同事進入.假設源源不斷的有同事進來的話,電梯就須要處於一種待機的狀態,一直等待人員的進入,直到沒有新的同事進入或者說電梯滿了,這個時候,電梯才能運行.另外,同事的進入須要在電梯門的關閉以前,不然的話,就只能等下一趟了.
換成圖示咱們能夠這麼理解併發

debounce

上面一排方塊爲函數的調用,下面的方塊則是函數實際的運行.咱們能夠看到,即便函數屢次調用,在短暫的暫停後,函數只會運行一次.app

debounce運用場景

既然debounce函數能夠把屢次的函數調用壓縮成一次,那麼咱們在進行Markdown渲染的時候,就能夠排上用場了.若是咱們在每一次鍵盤的敲擊都進行一次Markdown渲染,必然會形成部分的計算冗餘,同時也可能由於屢次無畏的渲染致使頁面卡頓,影響體驗,於是咱們可使用debounce函數,把Markdown的渲染進行壓縮,只在鍵盤敲擊結束了必定的時間後((能夠完成一次詞語或者語句的輸入),再進行渲染,可以減小許多冗餘的計算,提升體驗.less

throttle電梯

throttle電梯不想debounce電梯同樣會無限的等待,而是咱們設定一個時間,例如10s,那麼10s內,其餘的人能夠不斷的進入電梯,可是,一旦10s過去了,那麼不管如何,電梯都會進入運行的狀態.socket

換成圖示,咱們能夠這麼理解函數

throttle
上面一排的方塊是函數的調用,咱們能夠看到,及時進行了屢次的函數調用,函數也只會在隔一段時間實際運行一次,不會每一次的函數調用都運行this

throttle運用場景

throttle也有另一個稱號,就是節流函數,顧名思義就是可以節省函數調用時的資源消耗,達到防止系統資源被一直大量佔用,從而影響其餘函數執行的狀況.throttle一個運用的比較普遍的場景則是經過對scroll函數進行節流,由於每一次滾動頁面,都有進行資源的消耗計算,可是徹底不必每一次滾動時間觸發的時候,都進行計算,這樣有可能會致使大量的計算堆積而出現跳幀的狀況發生,於是咱們須要使用throttle函數進行節流,在滾動事件發生了一段事件後,再統一的進行處理,只要時間設置的合理,用戶通常是感知不到的.spa

debounce與throttle的原理與實現

解釋的再多,也不如咱們直接本身實現一遍debouncethrottle,這樣對於兩個函數的運用和理解,都會更上一層樓.debouncethrottle在許多的庫,例如jQuery,loadash以及underscore中都有實現,這裏採用underscore1.1.3版本的實現,很是簡單並且可以達到目的(其實主要是最近在看underscore源碼)
代碼以下code

// throttle 和 debouce 函數的底層實現
    var limit = function(func, wait, debounce) {
        var timeout;
        return function() {
            var context = this, args = arguments;
            // 封裝函數,用於延遲調用
            var throttler = function() {
                // 只是節流函數的時候,對其timeout進行賦值爲null,這樣能夠設置下一次的setTimtout
                timeout = null;
                func.apply(context, args);
            };
            // 若是debouce是true的話,前一個函數的調用timeout會被清空,不會被執行
            // 就是debounce函數的調用,這個前一個函數的不會執行.下面會從新設定setTimeout用於
            // 執行這一次的調用.
            // 可是若是是throttle函數,則會執行前一個函數的調用,同時下面的setTimeout在
            // 函數沒有運行的時候,是沒法再次設定的.
            if (debounce) clearTimeout(timeout);
            // 若是debouce是true 或者 timeout 爲空的狀況下,設置setTimeout
            if (debounce || !timeout) timeout = setTimeout(throttler, wait);
        };
    };

    // throttle 節流函數
    _.throttle = function(func, wait) {
        return limit(func, wait, false);
    };

    // debouce 屢次調用,只執行最後一次.
    _.debounce = function(func, wait) {
        return limit(func, wait, true);
    };

代碼上面都加了註釋,比較好理解,並且也比較簡單.經過代碼,咱們能夠更加進一步的理解debouncethrottle的原理以及實現,主要都是經過標誌位來判斷是否要清空setTimeout以及是否要生成新的setTimeout

至此,debouncethrottle的原理以及實現基本就介紹完成了.寫的不是特別的流暢,你們湊合着看,主要仍是用於記錄在平常工做中以及在源碼閱讀中遇到的一些小發現和小靈感.

參考

jQuery throttle / debounce: Sometimes, less is more!

相關文章
相關標籤/搜索