从 Underscore.js 源码解读节流(throttle) 和防抖(debounce)函数

从 Underscore.js 源码解读节流 throttle 和防抖 debounce 函数

Underscore.js源码地址:https://unpkg.com/underscore@1.13.7/underscore.js

1.节流和防抖对比

  • 相同点:都是前端性能优化的一种手段,能够降低用户操作触发频次,从而提高效能
  • 不同点:实现方式有所不同
    • 节流:控制连续操作过程中,一段时间内只能执行一次
    • 防抖:控制频繁操作,中间间隔时间超过 delay 等待时间,才执行一次。即连续操作N次,只执行最后一次

具体效果可以看下面视频演示

2.节流的实现

underscore.js

// getTime() 方法可返回距 1970 年 1 月 1 日之间的毫秒数
var now = Date.now || function() {
  return new Date().getTime();
};
/**
 * 节流方法定义
 * @param {*} func 执行函数
 * @param {*} wait 等待时间
 * @param {*} options 配置参数
 * @returns 
 * 创建并返回一个像节流阀一样的函数,当重复调用函数的时候,至少每隔 wait 毫秒调用一次该函数。对于想控制一些触发频率较高的事件有帮助。
  默认情况下,throttle 将在你调用的第一时间尽快执行这个 func函数,并且,如果你在 wait 周期内调用任意次数的函数,都将尽快的被覆盖。
  如果你想禁用第一次首先执行的话,传递 {leading: false},
  还有如果你想禁用最后一次执行的话,传递 {trailing: false}。
 */
function throttle(func, wait, options={}) {
  // result 用于接收 func 函数的返回值
  var timeout, context, args, result; 
  // previous 上一次点击时刻的毫秒数
  var previous = 0; 
  if (!options) options = {};

  var later = function () {
  	// leading 为 false,禁用第一次首先执行
    previous = options.leading === false ? 0 : now(); 
    timeout = null;
    result = func.apply(context, args);
    if (!timeout) context = args = null;
  };

  var throttled = function () { //输入事件触发函数
  	// 获取当前时刻的毫秒数
    var _now = now(); 
    if (!previous && options.leading === false) {
      previous = _now;
    }  
    // 剩下需要等待的毫秒数 = 用户设置的触发间隔毫秒数 - 实际距离上一次点击间隔的毫秒数
    var remaining = wait - (_now - previous); 
    // 上下文this
    context = this; 
    // 函数内置对象:所有参数
    args = arguments; 
    // 等待间隔数已经超过等待时间了,可以直接执行操作
    if (remaining <= 0 || remaining > wait) { 
      if (timeout) { 
      	// 清除定时器
        clearTimeout(timeout);
        timeout = null;
      }
      // 保存当前点击时刻毫秒数,作为 下一次点击时 的 上一次点击时间
      previous = _now; 
      // 执行func
      result = func.apply(context, args); 
      if (!timeout) {
        context = args = null; 
      }
    } else if (!timeout && options.trailing !== false) { //还在等待时间内并且不禁用最后一次执行,用定时器延迟等待渡过剩余时间,再执行;
      // 注意:if(!timeout)是必要的,要判断当前有 timeout 就不重复创建,在上面超过等待时间时,才去清除定时器,避免重复创建
      timeout = setTimeout(later, remaining); // remaining:剩余等待毫秒数
    }
    return result;
  };

  // 取消预定的 throttle
  throttled.cancel = function () {
    clearTimeout(timeout);
    previous = 0;
    timeout = context = args = null;
  };

  return throttled;
}


3.防抖的实现

源码写法:

// 当返回函数的一系列调用结束时,将触发参数函数。序列的结束由 wait 参数定义。如果传入 immediate,则参数函数将在序列的开始而不是结束时触发。
function debounce(func, wait, immediate) {
  var timeout, previous, args, result, context;

  var later = function () {
    // 距上一次点击的时间
    var passed = now() - previous;
    // 如果没到 wait等待时间,就接着等待
    if (wait > passed) {
      // 递归调用,等待时间 = 总共需等待时间wait - 已等待时间passed
      timeout = setTimeout(later, wait - passed);
    } else { // 够等待时间了,调用 func
      timeout = null;
      if (!immediate) result = func.apply(context, args);
      // 这个检查是必要的,因为 func 可以递归地调用 debounced 
      if (!timeout) args = context = null;
    }
  };
	
  // 源码这里调用了 restArguments方法获取剩余参数,这里简化了
  var debounced = function (..._args) {
    context = this;
    args = _args;
    previous = now();
    if (!timeout) {
      timeout = setTimeout(later, wait);
      // immediate为true,立即执行
      if (immediate) result = func.apply(context, args);
    }
    return result;
  };

  debounced.cancel = function () {
    clearTimeout(timeout);
    timeout = args = context = null;
  };

  return debounced;
}

整体简化版本:更好理解

// 防抖:wait等待时间内如果再次操作,就取消上次计时,重新timer计时,直到停止操作等待wait时间后,再执行
function debounce(fn, wait, immediate) {
  let context, args, timer;

  let debounced = function () {
    context = this;
    args = arguments;
    if (timer) {
      clearTimeout(timer);
      timer = null;
    }
    // immediate为true,且立即执行
    if (immediate && !timer) { 
      fn.apply(context, args)
    }
    timer = setTimeout(() => {
      fn.apply(context, args)
      clearTimeout(timer);
      timer = null;
    }, wait)


  }

  // 取消预订的 debounce
  debounced.cancel = function () {
    clearTimeout(timer);
    context = args = timer = null;
  }

  return debounced
}

节流防抖函数调用

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div>
    节流:<input style="width: 90%;" id="input" type="text" oninput="throttled()">
  </div>
  <div>
    防抖:<input style="width: 90%;" id="input2" type="text" oninput="debounced()">
  </div>
  <script src="./underscore.js"></script>
  <script>
  	var inputDom = document.getElementById("input");
  	var inputDom2 = document.getElementById("input2")
    function func() {
      console.log(inputDom.value);
    }
    function func2() {
      console.log(inputDom2.value);
    }
    var throttled = throttle(func, 1000);
    var debounced = debounce(func2, 1000);
  </script>
</body>

</html>

4.效果演示

节流防抖函数效果演示

5.应用场景

  • 节流:鼠标拖拽或滚动加载事件,降低调用执行函数的频率;
  • 防抖:input输入框,边输入边请求后台,等输入中断一定时间再请求,连续输入就不重复请求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值