javascript之函数防抖与节流

342 阅读3分钟

为了解决什么问题?

在平时的开发中,经常会用到 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;
        }
    }

结语:

这里就函数的防抖与节流分别以时间戳和定时器的方式实现,主要是为了解防抖与节流的区别,以及不同思路的实现过程。