Day7 - 弄懂JS的防抖和节流

对这两个的定义一直是理解以后又忘记,因此就记下来方便下次查看javascript

防抖(debounce)

概念:在指定的时间内只执行一次回调函数,若在指定时间内又触发了该事件,则会基于此刻从新计算回调函数的执行时间。html

以咱们生活中乘车刷卡的情景举例,只要乘客不断地在刷卡,司机师傅就不能开车,乘客刷卡完毕以后,司机会等待几分钟,肯定乘客坐稳再开车。若是司机在最后等待的时间内又有新的乘客上车,那么司机等乘客刷卡完毕以后,还要再等待一会,等待全部乘客坐稳再开车。java

// fn是咱们须要包装的事件回调, delay是每次推迟执行的等待时间
function debounce(fn, delay) {
  let timer = null
  return function () {
    let context = this, args = arguments
    timer && clearTimeout(timer)
    timer = setTimeout(function () {
      fn.apply(context, args)
    }, delay)
  }
}

复制代码

节流(throttle)

概念:预先设定一个执行周期,只有触发事件间隔大于等于这个执行周期,才会执行回调函数app

类比到生活中的水龙头,拧紧水龙头到某种程度会发现,每隔一段时间,就会有水滴流出。函数

// 一、 使用setTimeout实现
function throttle (fn, delay) {
  let flag = false
  if (!flag) {
    return function() {
      let context = this, args = arguments
      flag = setTimeout(() => {
        fn.apply(context, args)
        flag = true
      }, delay)
    }
  }
}


// 二、 经过比较两次时间戳的间隔是否大于等于咱们事先指定的时间来决定是否执行事件回调
function throttle (fn, delay) {
  let start = 0
  return function () {
    if (now - start >= delay) {
        let context = this, args = arguments, now = new Date()
        fn.apply(context, args);
        start = now
    }
  }
}

复制代码

对比两种实现方式,能够发现:ui

一、定时器的方式,只有在第一次触发回调的时候才会执行,若是最后一次触发与前一次触发时间间隔小于delay,则在delay以后fn也会执行。this

二、时间戳的方式,在页面加载的时候就会开始计时,若是页面加载时间大于delay,第一次触发事件回调就会当即执行,不会延迟delay,若是最后一次触发与前一次触发时间间隔小于delayfn并不会执行。spa

因此咱们将二者结合起来就能够实现,首次触发事件能够执行,最后的时间间隔小于delay也能够执行。code

function throttle (fn, delay) {
  let start = 0, timer = null
  
  return function() {
    let context = this, args = arguments, now = new Date()
    
    // 计算剩余时间
    let remaining = delay - (now - start)
    
    // 若是时间间隔超出了咱们设定的时间间隔阈值,那就不等了,不管如何要反馈给用户一次响应
    if (remaining <= 0) {
      timer && clearTimeout(timer)
      timer = null;
      fn.apply(context, args)
      start = now
      
    } else {
      // 若是时间间隔小于咱们设定的时间间隔阈值,则为本次触发操做设立一个新的定时器
	timer = setTimeout(() => {
            fn.apply(context, args)
        }, delay)
    }
  }
}

复制代码

应用场景

函数防抖是某一段时间内只执行一次,而函数节流是间隔时间执行htm

  • 函数防抖
    • 实时搜索,拖拽
  • 函数节流
    • 窗口调整(调整大小),页面滚动(滚动),抢购时疯狂点击(鼠标按下)

underscore实现

一、_.throttle函数

_.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;
    };
  };
复制代码

2.、_.debounce函数

_.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)与函数去抖(debounce)

十分钟学会防抖和节流

相关文章
相关标签/搜索