防抖节流(回顾)

防抖

定义

强制函数在固定时间只执行一次,多余执行无效

应用场景:

  1. 按钮的防二次点击操作 视频参考

实现思路:

  1. 开启一个延时器,只要延时器还在,不管怎么点击都不执行回调函数
  2. 一旦延时器结束并设置为 null,就可以再次点击
  3. 需要考虑的点:this指向, 事件源对象的获取(参数)
    在这里插入图片描述
//ES6
function debounce(fn, delay, timer = null) {
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(fn.bind(this, ...args), delay);
  };
}
//ES3 推荐
function debounce(fn, delay, timer = null) {
  return function () {
    let self = this
    let arg = arguments
    clearTimeout(timer);
    timer = setTimeout(function () {
      fn.apply(self, arg);
    }, delay);
  };
}

_.debounce 源码

// 使用 underscore 的源码来解释防抖动
/**
 * underscore 防抖函数,返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行
 *
 * @param  {function} func        回调函数
 * @param  {number}   wait        表示时间窗口的间隔
 * @param  {boolean}  immediate   设置为ture时,是否立即调用函数
 * @return {function}             返回客户调用函数
 */
_.debounce = function(func, wait, immediate) {
    var timeout, args, context, timestamp, result;

    var later = function() {
      // 现在和上一次时间戳比较
      var last = _.now() - timestamp;
      // 如果当前间隔时间少于设定时间且大于0就重新设置定时器
      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();
      // 如果定时器不存在且立即执行函数
      var callNow = immediate && !timeout;
      // 如果定时器不存在就创建一个
      if (!timeout) timeout = setTimeout(later, wait);
      if (callNow) {
        // 如果需要立即执行函数的话 通过 apply 执行
        result = func.apply(context, args);
        context = args = null;
      }

      return result;
    };
  };

节流

定义

强制函数以规定频率执行

应用场景:

  1. scroll事件,每隔一秒计算一次位置信息
  2. 浏览器播放事件,每一秒计算一次进度信息

实现思路:

  1. 设置一个lock,只要lock还在,就不执行延时器里的回调函数
  2. 每隔一段时间,就把lock打开,执行延时器中的回调函数
  3. 需要考虑的点:this指向, 事件源对象的获取(参数)
//ES6
function throttle(fn, delay) {
  let lock = false;
  return function (...args) {
    if (lock) return;
    fn.apply(this, args);
    lock = true;
    setTimeout(() => (lock = false), delay);
  };
}
//ES3
function throttle(fn, delay) {
  var lock = false;
  return function () {
    if (lock) return;
    fn.apply(this, arguments);
    lock = false;
    setTimeout(() => (lock = true), delay);
  };
}

_.throttle

/**
 * underscore 节流函数,返回函数连续调用时,func 执行频率限定为 次 / wait
 *
 * @param  {function}   func      回调函数
 * @param  {number}     wait      表示时间窗口的间隔
 * @param  {object}     options   如果想忽略开始函数的的调用,传入{leading: false}。
 *                                如果想忽略结尾函数的调用,传入{trailing: false}
 *                                两者不能共存,否则函数不能执行
 * @return {function}             返回客户调用函数   
 */
_.throttle = function(func, wait, options) {
    var context, args, result;
    var timeout = null;
    // 之前的时间戳
    var previous = 0;
    // 如果 options 没传则设为空对象
    if (!options) options = {};
    // 定时器回调函数
    var later = function() {
      // 如果设置了 leading,就将 previous 设为 0
      // 用于下面函数的第一个 if 判断
      previous = options.leading === false ? 0 : _.now();
      // 置空一是为了防止内存泄漏,二是为了下面的定时器判断
      timeout = null;
      result = func.apply(context, args);
      if (!timeout) context = args = null;
    };
    return function() {
      // 获得当前时间戳
      var now = _.now();
      // 首次进入前者肯定为 true
	  // 如果需要第一次不执行函数
	  // 就将上次时间戳设为当前的
      // 这样在接下来计算 remaining 的值时会大于0
      if (!previous && options.leading === false) previous = now;
      // 计算剩余时间
      var remaining = wait - (now - previous);
      context = this;
      args = arguments;
      // 如果当前调用已经大于上次调用时间 + wait
      // 或者用户手动调了时间
 	  // 如果设置了 trailing,只会进入这个条件
	  // 如果没有设置 leading,那么第一次会进入这个条件
	  // 还有一点,你可能会觉得开启了定时器那么应该不会进入这个 if 条件了
	  // 其实还是会进入的,因为定时器的延时
	  // 并不是准确的时间,很可能你设置了2秒
	  // 但是他需要2.2秒才触发,这时候就会进入这个条件
      if (remaining <= 0 || remaining > wait) {
        // 如果存在定时器就清理掉否则会调用二次回调
        if (timeout) {
          clearTimeout(timeout);
          timeout = null;
        }
        previous = now;
        result = func.apply(context, args);
        if (!timeout) context = args = null;
      } else if (!timeout && options.trailing !== false) {
        // 判断是否设置了定时器和 trailing
	    // 没有的话就开启一个定时器
        // 并且不能不能同时设置 leading 和 trailing
        timeout = setTimeout(later, remaining);
      }
      return result;
    };
  };

防抖和节流的区别

防抖:防止抖动,单位时间内事件触发会被重置,避免事件被触发多次,重在清零
节流:控制流量,单位时间内事件只能触发一次,重在开关锁

scroll 事件下的区别

视频参考

mousemove 事件下的区别

视频参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柳晓黑胡椒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值