函数节流和函数防抖

这段时间去面试了两家当前比较拽的互联网公司,回来一总结,广度略有,深度堪忧。这么看来可能觉得还是好事,最近没事在掘金上看看,不知不觉的关注的标签已经有40来个了。

eslint,requirejs,three,Web Components,函数式编程,Visual Studio Code,JSON,TypeScript,vuex,WebAssembly,HTTPS,WebGL,DOM,Canvas,敏捷开发,MVVM,React Native,响应式设计,HTTP,全栈,稀土,电子书,chrome,微信,代码规范,CSS,正则表达式,Node.js, 前端框架, HTML,设计模式,面试,程序员,算法,架构,Webpack......

bla bla bla......

前端就是苦,前端就是累。代码大家都能写,写出什么样的代码,那就是功力。

闲话说的太多了,前端时间自己用vue写了个相当简单的web音乐播放器,写完毕以后一直有几个问题困扰我。

1.vue如何高可用的组件开发。

2.自己写的搜索(autoComplete),返回数据不准确,可能返回以前关键字查询的结果。

3. vuex 如何在错误的时候不阻止程序的继续执行。

这里我说的是第二个问题,

我原来的考虑,搜索的时候传递关键字过去, 返回的时候,除了被搜索到的数据,还包括关键字本身。

如果当前的输入框的值和返回的数据的关键字匹配,那么展现,反之丢弃。

这里也有问题,如果关键字前后两次相同,那就会数据填充两次。比如关键字为  a->b->a,第一个a落后于b返回。但至少保证了展现的数据是自己期望的数据。

后来看了一下

百度的搜索,也是每次都发送请求,但是一些前面发送的请求会被取消掉,返回html和脚本

bing的搜索,也是每次都发送请求,没有取消,返回数据,html和脚本。

搜狗,每次发送请求,没有取消,返回的是数据+脚本。

但是都一个特点,就是返回的都足够快,这就有点尴尬了。

回头再看看自己搜索的问题,才发现问题的是自己的逻辑处理问题

1.当字符为空的时候没有发请求但是没有清空数据

2.oninput,focus处理有问题

晕死,一天在掘金看到函数节流和函数防抖,本事想应用到这个及时搜索上面来,可是啊哈,走歪了。

回归,我们来说说函数节流和函数防抖。

函数节流 & 函数防抖

Throttling enforces a maximum number of times a function can be called over time. As in "execute this function at most once every 100 milliseconds".

Debouncing enforces that a function not be called again until a certain amount of time has passed without it being called. As in "execute this function only if 100 milliseconds have passed without it being called".

函数节流:是确保函数特定的时间内至多执行一次

函数防抖:是函数在特定的时间内不被再调用后执行

函数节流underscore的实现(解释,借用的是JS魔法堂:函数节流(throttle)与函数去抖(debounce))

复制代码
.throttle = function(func, wait, options) {
    /* options的默认值
     *  表示首次调用返回值方法时,会马上调用func;否则仅会记录当前时刻,当第二次调用的时间间隔超过wait时,才调用func。
     *  options.leading = true;
     * 表示当调用方法时,未到达wait指定的时间间隔,则启动计时器延迟调用func函数,若后续在既未达到wait指定的时间间隔和func函数又未被调用的情况下调用返回值方法,则被调用请求将被丢弃。
     *  options.trailing = true; 
     * 注意:当options.trailing = false时,效果与上面的简单实现效果相同
     */
    var context, args, result;
    var timeout = null;
    var previous = 0;
    if (!options) options = {};
    var later = function() {
      previous = options.leading === false ? 0 : _.now();
      timeout = null;
      result = func.apply(context, args);
      if (!timeout) context = args = null;
    };
    return function() {
      var now = _.now();
      if (!previous && options.leading === false) previous = now;
      // 计算剩余时间
      var remaining = wait - (now - previous);
      context = this;
      args = arguments;
      // 当到达wait指定的时间间隔,则调用func函数
      // 精彩之处:按理来说remaining <= 0已经足够证明已经到达wait的时间间隔,但这里还考虑到假如客户端修改了系统时间则马上执行func函数。
      if (remaining <= 0 || remaining > wait) {
        // 由于setTimeout存在最小时间精度问题,因此会存在到达wait的时间间隔,但之前设置的setTimeout操作还没被执行,因此为保险起见,这里先清理setTimeout操作
        if (timeout) {
          clearTimeout(timeout);
          timeout = null;
        }
        previous = now;
        result = func.apply(context, args);
        if (!timeout) context = args = null;
      } else if (!timeout && options.trailing !== false) {
        // options.trailing=true时,延时执行func函数
        timeout = setTimeout(later, remaining);
      }
      return result;
    };
  };
复制代码

 略有复杂,我们当然有简单版本 ,基本的思想就是利用闭包生成一个新的执行函数,闭包里面记录上一次的调用时间,再次调用时时间差和允许的调用周期比较。

复制代码
function throttle(method,delay,duration){
  var timer=null, begin=new Date();
  return function(){
    var context=this, args=arguments, current=new Date();;
    clearTimeout(timer);
    if(current-begin>=duration){
       method.apply(context,args);
       begin=current;
    }else{
      timer=setTimeout(function(){
        method.apply(context,args);
      },delay);
    }
  }
}
复制代码

函数防抖在underscore的实现,其基本思路,就是内部计时,达到指定时间,就执行,不然启用延时。

复制代码
_.debounce = function(func, wait, immediate) {
    // immediate默认为false
    var timeout, args, context, timestamp, result;

    var later = function() {
      // 当wait指定的时间间隔期间多次调用_.debounce返回的函数,则会不断更新timestamp的值,导致last < wait && last >= 0一直为true,从而不断启动新的计时器延时执行func
      var last = _.now() - timestamp;

      if (last < wait && last >= 0) {
        timeout = setTimeout(later, wait - last);
      } else {
        timeout = null;
        if (!immediate) {
          result = func.apply(context, args);
          if (!timeout) context = args = null;
        }
      }
    };

    return function() {
      context = this;
      args = arguments;
      timestamp = _.now();
      // 第一次调用该方法时,且immediate为true,则调用func函数
      var callNow = immediate && !timeout;
      // 在wait指定的时间间隔内首次调用该方法,则启动计时器定时调用func函数
      if (!timeout) timeout = setTimeout(later, wait);
      if (callNow) {
        result = func.apply(context, args);
        context = args = null;
      }

      return result;
    };
  };
复制代码

那么应用场景:

函数节流(throttle

1. 频繁的mousemove/keydown,比如高频的鼠标移动,游戏射击类的

2. 搜索联想(keyup)

3. 进度条(我们可能不需要高频的更新进度)

4. 拖拽的dragover等

5.  高频的点击,抽奖等(哈哈,邪恶)

函数防抖(debounce

 1. scroll/resize事件

 2. 文本连续输入,ajax验证/关键字搜索

参考:

函数节流去抖与函数柯里化

浅谈 Underscore.js 中 _.throttle 和 _.debounce 的差异

JS魔法堂:函数节流(throttle)与函数去抖(debounce)

 The Difference Between Throttling and Debouncing

Javascript 函数节流和函数去抖场景介绍

原文地址:https://www.cnblogs.com/wangchaoyuana/p/7497377.html