防抖和节流

手写防抖和节流

简介

防抖(防止抖动)

  • 操作暂停/结束过一段时间才发起请求。限制执行次数,多次密集的触发只执行一次。
  • 应用场景:搜索时,当输入暂停时才发起请求,连续不停的输入时不发起请求。

节流

  • 操作过程中,每间隔固定时间才发起请求。限制执行频率,有节奏的执行。
  • 应用场景:在 dragscroll 时通常需要节流,以 drag 为例,拖动过程中,并不需要一直发起请求,可使用节流以固定时间间隔才发起请求。

他们的使用场景通常不一样,但容易搞混淆。

以拖动为例,只要拖动就会离开原位置很多 px,不存在只拖动 1px,所以他是连续的、一次操作会执行多次的,就应该使用固定时间间隔来实现,以节省资源,这就需要用节流。


目的/背景

lodash 工具库实现了防抖节流的工具函数。

很多时候我们需要自己实现这些小的工具函数。既然有工具函数可以实现,为什么还要自己来重复造轮子来实现呢?

想象这样的场景:我们在开发一个组件,这个组件依赖了 lodash ,那我们把开发好的组件发布到 npm 仓库后,别人使用我们的仓库就需要安装 lodash,这个 lodash 和他原来安装的 lodash 可能版本不一样,导致他原来是用的版本失效,出现 bug。所以我们依赖越少越。github 中也有这样的去除工具函数的一些方法 去除lodash

实现

防抖

<input type="text" id="input" />
const input = document.getElementById("input");
input.addEventListener("keyup", () => {
    console.log(input.value);
});

上面模拟普通的输入发请求(这里用打印替代)。

// 使用定时器实现防抖
const input = document.getElementById("input");
let timer = null;
input.addEventListener("keyup", () => {
    if (timer) {
        clearTimeout(timer);
    }
    timer = setTimeout(() => {
        console.log(input.value);
        timer = null;
    }, 500)
});

当连续两次及以上操作时,第一次操作 timer 没值,将会赋值一个定时器返回值,将在 500 ms 后执行逻辑。
如果 500 ms 内有了第二次操作,这时候 timer 有值(是第一次赋值的),定时器将会被清除,第一步将要被执行的逻辑停止执行。
第二次 timer 将会被重新附上值,如果 500ms 内没有新的操作,就将执行内部逻辑。并把 timer 重置。下一次将会重新计算 timer 有没有值,重复上面步骤。

为了不每一个需要用到的地方都这样写一遍防抖逻辑,需要将其封装为一个方法,需要的地方直接调用。

// 封装这个 debounce 函数(方法)

// 传入的 fn 函数表示延迟一定时间后需要执行的逻辑
function debounce(fn, delay = 500) {
    // timer 处于闭包中
    let timer = null;

    // 函数作为返回值,形成了闭包
    return function() {
        if (timer) {
            clearTimeout(timer);
        }
        timer = setTimeout(() => {
            fn();
            timer = null;
        }, delay)
    }
}

input.addEventListener(
  "keyup",
  debounce((e) => {
    // 第一个参数(函数)如果有参数,这个参数将会传给 debounce 返回的函数
    console.log(e.target);
    console.log(input.value);
  }, 1000)
);

节流

<!-- 给元素设置 draggable="true" 后,该元素便可以拖动了 -->
<div id="elem" class="elem" draggable="true">可拖拽元素</div>
elem.addEventListener("drag", (e) => {
    console.log(e.offsetX, e.offsetY);
});

上面代码没有任何节流措施,当拖动元素时,将一直不停的打印信息。

let timer = null;
  elem.addEventListener("drag", (e) => {
  // 拖动第一 px 以外情况,400ms 以内的其他拖动,就什么都不做直接返回(这时候 timer 已经有值了)
  // 400ms 以后,就会执行逻辑,并把 timer 重置,并循环上一次逻辑(又等待 400ms 再执行,并把 timer 重置)
  if (timer) {
    return;
  }
  timer = setTimeout(() => {
    console.log(e.offsetX, e.offsetY);
    timer = null;
  }, 400);
});

同样的,我们可以封装成工具函数,供多个地方使用。

function throttle(fn, delay = 500) {
    // timer 处于闭包中
    let timer = null;

    // 函数作为返回值,形成了闭包
    return function () {
      if (timer) {
        return;
      }
      timer = setTimeout(() => {
        // fn(); // 这样写接受不到 fn 函数中传入的参数
        fn.apply(this, arguments);
        timer = null;
      }, delay);
    };
}
elem.addEventListener(
    "drag",
    throttle((e) => {
      // 第一个参数(函数)如果有参数,这个参数将会传给 throttle 返回的函数
      console.log(e.offsetX, e.offsetY);
    }, 200)
);
posted @ 2022-04-01 18:18  艾前端  阅读(85)  评论(0编辑  收藏  举报