JavaScript之防抖和节流

在某些特殊情况下,会出现高频操作。比如监听器在监听页面的滚动或者浏览器窗口尺寸变化时,页面滚动或者浏览器窗口变化时会使得监听器出现高频操作,这时候会非常消耗浏览器的性能,而且可能会出现错误。所以需要限制高频操作,防止出现一些不是预期的情况,

目录
  1. 什么是节流和防抖
  2. 防抖
  3. 节流
  4. 使用场景
  5. 注意事项

什么是节流和防抖#

在某些特殊情况下,会出现高频操作。比如监听器在监听页面的滚动或者浏览器窗口尺寸变化时,页面滚动或者浏览器窗口变化时会使得监听器出现高频操作,这时候会非常消耗浏览器的性能,而且可能会出现错误。所以需要限制高频操作,防止出现一些不是预期的情况,或者节约性能。

防抖和节流就可以限制这些不必要的高频操作的方法。

防抖#

触发高频事件后 n 秒内函数只会执行一次,如果 n 秒内高频事件再次被触发,则重新计算时间。

实现思路:每次触发事件时都取消之前的延时调用方法

function debounce(fn, wait = 200) {
  let timeoutId = null; // 维护一个标记用来存放定时器的返回值
  return function () {
    clearTimeout(timeoutId); // 每次执行程序前,把上一个 setTimeout 清除掉,重新开始计时
    timeoutId = setTimeout(() => {
      fn.apply(this, arguments);
    }, wait);
  };
}

其他要求:如果需要先立即执行一次呢?

实现思路:维护一个变量,先执行一次函数,然后再实现防抖。

const debounce = (fn, wait = 200) => {
  let timeoutId, inDebounce;
  return function (...args) {
    if (!inDebounce) {
      fn.apply(this, args); // 先执行一次
      inDebounce = true; // 执行之后在打开防抖开关
    } else {
      clearTimeout(timeoutId);
      timeoutId = setTimeout(() => fn.apply(this, args), wait);
    }
  };
};

节流#

高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率。

实现思路:每次触发事件时都判断当前是否有等待执行的延时函数

function throttle(fn, wait = 200) {
  let canRun = true; // 维护一个标记
  return function () {
    if (!canRun) return; // 在函数开头判断标记是否为 true,不为 true 则 return
    canRun = false; // 为True的话,立即设置为 false
    setTimeout(() => {
      fn.apply(this, arguments);
      canRun = true; // 每次执行完程序后必须使 canRun 为 true,可以开始下次计时
    }, wait);
  };
}

其他要求:如果需要先立即执行一次呢?

实现思路:同理,维护一个变量,先执行一次函数,然后再实现节流。

const throttle = (fn, wait = 200) => {
  let inThrottle, lastFn, lastTime;
 
  return function () {
    const context = this,
      args = arguments;
 
    if (!inThrottle) {
      fn.apply(context, args); // 先执行一次
      lastTime = Date.now(); // 执行完成后开始计时
      inThrottle = true; // 打开节流开关,开始节流操作
    } else {
      clearTimeout(lastFn); // 先清除上次的定时器
 
      lastFn = setTimeout(function () {
        // 时间到了再执行
        if (Date.now() - lastTime >= wait) {
          fn.apply(context, args);
          lastTime = Date.now(); // 执行完成后重置时间
        }
      }, Math.max(wait - (Date.now() - lastTime), 0)); // 延迟时间:定时 - (现在时间 - 上次执行的时间) 或者 0
    }
  };
};

使用场景#

  • 防抖

    • search 搜索联想,用户在不断输入值时,用防抖来节约请求资源。
    • window 触发 resize 的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
  • 节流

    • 鼠标不断点击触发,单位时间内只触发一次。
    • 监听滚动事件,比如是否滑到底部自动加载更多,可以用节流来判断。

注意事项#

在使用防抖和节流限制监听事件的高频操作时,在页面卸载时,还需要删除监听事件。

以 Vue 为例:

<script>
  export default {
    created() {
      // 监听滚动
      this.onScroll = window.addEventListener(
        'scroll',
        throttle(this.loadMore, 300)
      )
    },
 
    beforeDestroy() {
      // 删除监听器
      if (this.onScroll) {
        window.removeEventListener('scroll', this.loadMore)
      }
    },
 
    methods: {
      loadMore() {
        // code
      }
    }
</script>

把监听的方法提取出来,在监听和删除监听时都可以很好的使用。