underscore源码之学习防抖

121 阅读3分钟

underscore源码之学习防抖

前言

**本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。**这是源码共读的第25期,链接:juejin.cn/post/708744…

了解underscore库

underscore是一个javascript的函数式编程助手库,版本1.13.7

什么是防抖\节流

防抖本质上是优化高频率代码执行的一种手段,浏览器的resizescrollkeypressmousemove 等事件在触发时,会不断地调用绑定在事件上的回调函数,极大地浪费资源,降低前端性能,

  • 防抖:延迟n秒后再触发事件函数,如果n秒内又触发了该事件,则重新计时n秒
  • 节流:n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效

手写防抖函数

  1. 简单版本
  • 频繁的调用debounce函数,就清空定时器,重新计时执行
function debounce(fun, wait) {
  let timeout = null;

  return function () {
    if(timeout) clearTimeout(timeout);
    timeout = setTimeout(() => {
      fun.apply(this, arguments);
    }, wait);
  }
}
  1. 进阶版本,增加immediate
function debounced(func, wait, immediate) {

  let timeout = null
  return function(...args) {
  
    // 已经注入了定时器,则清空
    if (timeout) clearTimeout(timeout)
    
    // 立即执行逻辑
    if (immediate) {
      let callNow = !timeout
      timeout = setTimeout(() => {
        timeout = null
      }, wait)
      // 第一次调用立即执行func, 后面要等timeout = null执行之后才会调用func函数
      if (callNow) func.apply(this, args)
    } else {
      timeout = setTimeout(() => {
        func.apply(this, args)
      }, wait)
    }
  }
}

debounce.js源码解读

  1. later函数的时间计算更加严谨
import restArguments from './restArguments.js';
import now from './now.js';

// 导出debounce函数,debounce函数里有三个形参,fun<需要防抖的函数>、wait<等待的时间,毫秒单位>、
// immediate<是否立即执行>
export default function debounce(func, wait, immediate) {
  var timeout, previous, args, result, context;

  var later = function() {
    // 计算调用debounced函数的时候至目前later调用已经过了多少时间,
    var passed = now() - previous;
    if (wait > passed) {
      // 如果当前等待时间 < wait, 则继续注入setTimeout等待wait - passed时间后再执行
      timeout = setTimeout(later, wait - passed);
    } else {
    // 清空timeout, 防止注入了多的setTimeout
      timeout = null;
      // 判断不是immediate,执行func并缓存result, 这里指的是只要在wait时间内且immediate为true的情况下调用过func, 被防抖函数fun都不会再次执行,
      if (!immediate) result = func.apply(context, args);
      // timeout不为空的情况下,设置args和context为null
      if (!timeout) args = context = null;
    }
  };

  var debounced = restArguments(function(_args) {
    context = this;
    args = _args;
    previous = now();
    if (!timeout) {
      // 注入一个setTimeout函数,等待wait时间后执行later函数
      timeout = setTimeout(later, wait);
      
      // 如果存在传参immediate为true, 则立即调用func函数并记录返回结果
      if (immediate) result = func.apply(context, args);
    }
    return result;
  });
    
  // debounced函数增加cancel属性函数来取消函数的防抖效果
  debounced.cancel = function() {
    clearTimeout(timeout);
    timeout = args = context = null;
  };

  return debounced;
}

  1. debounced还引用了restArgements是一个函数
  • 这个函数 restArguments 的作用是将一个函数的参数分为固定参数剩余参数,并将剩余参数打包成一个数组传递给原函数。它的功能类似于 ES6 中的 Rest Parameters(剩余参数)语法(...rest),但在 ES6 之前,JavaScript 并没有原生支持剩余参数,因此需要手动实现
export default function restArguments(func, startIndex) {

  // 如果没有startIndex, 则将函数的最后一个参数作为剩余参数
  startIndex = startIndex == null ? func.length - 1 : +startIndex;
  return function() {
    var length = Math.max(arguments.length - startIndex, 0),
        rest = Array(length),   // 声明存储剩余参数的数组
        index = 0;
    for (; index < length; index++) {
      rest[index] = arguments[index + startIndex]; // 将剩余参数存在rest数组中
    }
    switch (startIndex) {
      case 0: return func.call(this, rest); 
      case 1: return func.call(this, arguments[0], rest);
      case 2: return func.call(this, arguments[0], arguments[1], rest);
    }
    var args = Array(startIndex + 1);
    for (index = 0; index < startIndex; index++) {
      args[index] = arguments[index];
    }
    args[startIndex] = rest;
    
    // apply的传参需要时数组,将所有参数整合到数组args中
    return func.apply(this, args);
  };
}

结语

多积累和学习总有收获,如有问题,欢迎指出,谢谢