javascript之函式防抖與節流

minghu發表於2018-11-20

為了解決什麼問題?

在平時的開發中,經常會用到 scroll、resize、keydown、keyup、mouseover、click等等一系列事件,但有的時候頻繁的去觸發事件會帶來資源、效能等等一些問題,比如:
1、做一個搜尋功能,避免不了需要做關聯詞的下拉展示,需要當使用者輸入的時候去請求後端介面,這時就可以適當對鍵盤事件的觸發函式做相應的控制,對於使用者來說是察覺不到的,這樣可以減少不必要的http請求。
2、做一個提交按鈕,肯定會考慮到使用者的連續點選,這就可能造成重複提交的問題,正常情況下服務端是都需要做這方面的控制,前端也是必不可少的。
......

防抖與節流的區別

  • 相同點:

都是為了減少函式被觸發的頻次。

  • 異同點:

防抖:函式首次被觸發執行過後,就算很頻繁的觸發,我也會等你停下來不觸發這個函式一段時間後,再次觸發的時候才執行。
節流:函式首次被觸發執行過後,就算很頻繁的觸發,我也會過一段時間後,再次觸發的時候才執行。


下面主要是以計算時間戳和定時器兩種方式來分別實現函式的防抖與節流。

一、防抖

實現過程

  • 實現方案一:

通過計算時間戳的方式實現,函式觸發時立即執行。

    'use strict';
    
    function debounce(func, wait) {
        var time = 0, result;
        return function() {
            var args = [].slice.call(arguments),
                nowTime = Date.now();
            if(nowTime - time >= wait) {
                result = func.apply(this, args);
            }
            //每一次執行都需要更新時間
            time = nowTime;
            
            return result;
        }
    }
複製程式碼
  • 實現方案二:

通過定時器實現,函式觸發時會等待一段時間後才執行

    'use strict';

    function debounce(func, wait) {
        var timeout;
        return function() {
            var context = this,
                args = [].slice.call(arguments);
            if(timeout) clearTimeout(timeout);
            timeout = setTimeout(function() {
                func.apply(context, args);
            }, wait);
        }
    }
複製程式碼
  • 實現方案三:

通過定時器實現,函式觸發時會立即執行

    'use strict';
    
    function debounce(func, wait) {
        var timeout, result;
        return function() {
            var args = [].slice.call(arguments);
            if(!timeout) {
                result = func.apply(this, args);
            } else {
                clearTimeout(timeout);
            };
            timeout = setTimeout(function() {
                timeout = null;
            }, wait);
            
            return result;
        }
    }
複製程式碼
  • 最後來看一下 underscore 中是怎麼實現的:
    function debounce(func, wait, immediate) {
        var timeout, result;

        function debounced() {
            var context = this,
                args = [].slice.call(arguments);
          if (timeout) clearTimeout(timeout);
          if (immediate) {
            var callNow = !timeout;
            timeout = setTimeout(function() {
                timeout = null;
            }, wait);
            if (callNow) result = func.apply(this, args);
          } else {
            timeout = setTimeout(function() {
                func.apply(context, args)
            }, wait);
          }
    
          return result;
        };

        debounced.cancel = function() {
          clearTimeout(timeout);
          timeout = null;
        };

    return debounced;
  };
複製程式碼

不得不說這個函式還是考慮的比較全面的。相當於對方案二和方案三進行了整合,通過 immediate 引數來表示是否需要立即執行,並且增加了取消的方法。

二、節流

函式首次被觸發執行過後,就算很頻繁的觸發,我也會過一段時間後,再次觸發的時候才執行。

實現過程

  • 實現方案一:

通過計算時間戳實現,每當達到觸發條件,會立即執行。

    'use strict';
    
    function throttle(func, wait) {
        var time = 0, result;
        return function() {
            var args = [].slice.call(arguments),
                nowTime = Date.now();
                if(nowTime - time >= wait) {
                    result = func.apply(this, args);
                    //更新當前觸發 func 的時間
                    time = nowTime;
                }
                
            return result;
        }
    }
複製程式碼
  • 實現方案二:

通過定時器實現,每當達到觸發條件,會立即執行。

    'use strict';
    
    function throttle(func, wait) {
        var timeout, result;
        return function() {
            var args = [].slice.call(arguments),
                context = this;
            if(!timeout) {
                result = func.apply(context, args);
                timeout = setTimeout(function() {
                    clearTimeout(timeout);
                    timeout = null;
                }, wait);
            };
            
            return result;
        }
    }
複製程式碼
  • 方案三:

如果想達到條件之後,延遲一段時間後執行,只需將方案二的實現稍微改動即可。

    'use strict';
    
    function throttle(func, wait) {
        var timeout, result;
        return function() {
            var args = [].slice.call(arguments),
                context = this;
            if(!timeout) {
                timeout = setTimeout(function() {
                    clearTimeout(timeout);
                    timeout = null;
                    result = func.apply(context, args);
                }, wait);
            };
            
            return result;
        }
    }
複製程式碼

結語:

這裡就函式的防抖與節流分別以時間戳和定時器的方式實現,主要是為了解防抖與節流的區別,以及不同思路的實現過程。

相關文章