【若川视野 x 源码共读】第25期 | 跟着underscore学防抖

173 阅读4分钟

前言

本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。

定义

防抖和节流,都是为了防止短时间内高频繁调用同一接口的优化方案。

防抖

原理:设置延时器,短时间高频率触发只有最后一次触发成功

解释:比如信息流无限加载,滑至底部后,设置一个定时器,间隔1s再发送请求,如果 1s内多次请求,则清除定时器,1s后重新发送请求。也就是说,在1s内无论滑动至底部多少次,最后一次总是会清除上一次的延迟,继而进行自身请求。

无论你如何触底,也只有你停止滑屏后的最后一次会请求成功。

缺点:响应时间太久。

节流

原理:设置请求间隔,短时间高频率触发时,每间隔一定时间进行一次触发。

解释:节流就是设置状态锁,比如每隔1s执行一次,第一次触发后,在下一秒前阻断该期间内的所有请求,保证高频的请求每间隔一段时间进行一次请求,降低了请求频率。

防抖和节流区别

节流不管事件触发有多频繁,都会保证在规定时间内一定会真正执行一次事件处理函数,而函数防抖只是在最后一次触发后才会执行。

实现

防抖

实现原理就是利用定时器,函数第一次执行时设定一个定时器,之后调用时发现已经设定过定时器就清空之前的定时器,并重新设定一个新的定时器,如果存在没有被清空的定时器,当定时器计时结束后触发函数执行。

function debounce(original, wait) {
  var timer = null;
​
  return function () {
    var context = this, args = arguments;
​
    if (timer) { clearTimeout(timer) };
    
    timer = setTimeout(function () {
      original.apply(context, args)
    }, wait);
  }
}

为了解决防抖长时间才触发的问题,我们可以新增一个immediate参数,在触发函数后立即执行一次

function debounce(original, wait, immediate) {
  var timer = null;
​
  return function () {
    var context = this, args = arguments;
    
    if (timer) { clearTimeout(timer) };
​
    if (immediate && !timer) {
      original.apply(context, args)
    };
​
    timer = setTimeout(function () {
      original.apply(context, args);
      timer = null;
    }, wait)
  }
}

节流

规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。

// 时间比较法
function throttle (original, wait) {
  var pre = new Date();
​
  return function () {
    var now = new Date(), args = arguments;
    
    if(now - pre > wait) {
      pre = now;
      original.apply(this, args);
    }
  }
}
// 定时器法
function throttle (original, wait) {
  var timer = null;
​
  return function (...args) {
    var  args = arguments;
    
    if(timer) return;
​
    timer = setTimeout(() => {
      original.apply(this, args);
      timer = null;
    }, wait)
  }
}

underscore 源码解析

_.debounce = function(func, wait, immediate) {
  // timeout 表示定时器
  // result 表示 func 执行返回值
  var timeout, result;
​
  // 定时器计时结束后
  // 1、清空计时器,使之不影响下次连续事件的触发
  // 2、触发执行 func
  var later = function(context, args) {
    timeout = null;
    // if (args) 判断是为了过滤立即触发的
    // 关联在于 _.delay 和 restArguments
    if (args) result = func.apply(context, args);
  };
​
  // 将 debounce 处理结果当作函数返回
  var debounced = restArguments(function(args) {
    if (timeout) clearTimeout(timeout);
    if (immediate) {
      // 第一次触发后会设置 timeout,
      // 根据 timeout 是否为空可以判断是否是首次触发
      var callNow = !timeout;
      timeout = setTimeout(later, wait);
      if (callNow) result = func.apply(this, args);
    } else {
        // 设置定时器
      timeout = _.delay(later, wait, this, args);
    }
​
    return result;
  });
​
  // 新增 手动取消
  debounced.cancel = function() {
    clearTimeout(timeout);
    timeout = null;
  };
​
  return debounced;
};
​
// 根据给定的毫秒 wait 延迟执行函数 func
_.delay = restArguments(function(func, wait, args) {
  return setTimeout(function() {
    return func.apply(null, args);
  }, wait);
});

相比上文的基本版实现,underscore 多了以下几点功能。

  1. 函数 func 的执行结束后返回结果值 result
  2. 定时器计时结束后清除 timeout,使之不影响下次连续事件的触发
  3. 新增了手动取消功能 cancel
  4. immediate 为 true 后只会在第一次触发时执行,频繁触发回调结束后不会再执行

总结

  1. 函数防抖和函数节流都是防止某一时间频繁触发,但是这两兄弟之间的原理却不一样。
  2. 函数防抖是某一段时间内只执行一次,而函数节流是间隔时间执行。
  3. 函数节流和防抖都是闭包高阶函数的应用