防抖与节流详解:性能优化的利器!

94 阅读2分钟

文章会用到的主要知识有闭包与类的封装,可阅读前期文章阅读阮一峰教程:一文讲清JavaScript 闭包

防抖(Debounce)

防抖的核心思想是:在事件被频繁触发时,只在最后一次触发后的一段时间后执行一次函数。如果在这段时间内又被触发,则重新计时。

为什么要呢?因为不防的话,触发的频率太高,会直接造成服务器的"宕机"。

我们会经常遇见防抖,比如我们使用百度,谷歌或掘金的搜索引擎搜索:防抖,等待一会儿就会出现相关的内容。这种应用场景最初由Google提出,所以叫Google Suggest,一般来说叫做搜索框输入自动联想

image.png

还有许许多多的应用场景,如窗口大小调整(resize),表单验证等等。

代码简单实现

function debounce(fn, delay) {
  return function (args) {
    clearTimeout(fn.id);
    fn.id = setTimeout(() => {
      fn(args);
    }, delay);
  };
}

分析debounce 返回一个新函数,每次调用时都会清除上一次的定时器(clearTimeout(fn.id)),并重新设置一个新的定时器(setTimeout)。只有在最后一次触发后的 delay 毫秒后,fn 才会被执行。通过 fn.id 这个属性,利用闭包特性为每个函数单独保存定时器 id,避免全局变量污染

防抖时序图

sequenceDiagram
  participant 用户
  participant 防抖函数
  用户->>防抖函数: 连续输入
  防抖函数-->>防抖函数: 清除上一次定时器
  防抖函数-->>防抖函数: 重新设置定时器
  Note right of 防抖函数: 只有最后一次输入后 delay 毫秒才会执行

节流(Throttle)

节流的核心思想是:在一段时间内,无论事件被触发多少次,函数只会按照固定频率执行

如下图:

7.gif

就是一个应用场景,相关的应用场景还有页面滚动(scroll)事件拖拽(drag)事件处理高频点击事件等等。

代码实现与讲解

function throttle(fn, delay) {
  let last, deferTimer;
  return function () {
    let that = this;
    let now = +new Date();  // 类型转换
    let args = arguments;

    if (last && now < last + delay) {
      clearTimeout(deferTimer);
      deferTimer = setTimeout(function () {
        last = now;
        fn.apply(that, args);
      }, delay);
    } else {
      last = now;
      fn.apply(that, args);
    }
  };
}

代码分析:throttle 返回一个新函数,每次调用时会判断距离上一次执行是否超过 delay 毫秒。如果未超过,则通过 setTimeout 延迟执行,并清除上一次的延迟(保证只执行最后一次)。如果超过,则立即执行,并更新时间戳。通过闭包保存 lastdeferTimer,保证每个节流函数的独立性。

keyup节流示例:

let throttleAjax = throttle(ajax, 1000);
inputC.addEventListener("keyup", function (e) {
  throttleAjax(e.target.value);
});

如上例,输入框的 keyup 事件会被节流处理,避免高频率触发 ajax 请求。

节流时序图

sequenceDiagram
  participant 用户
  participant 节流函数
  用户->>节流函数: 高频触发
  节流函数-->>节流函数: 判断时间间隔
  alt 超过间隔
    节流函数->>ajax: 立即执行
  else 未超过间隔
    节流函数-->>节流函数: 延迟执行
  end

总结

防抖适合只关心最后一次操作的场景,节流适合需要定期响应的场景。两者都有普遍的应用场景

闭包是实现防抖与节流的关键技术,能有效管理私有变量和状态。