防抖(Debounce)和节流(Throttle)

发布于 1 个月前

防抖和节流这两种东西在前端性能优化比较常见,是一种基本的优化手段。

防抖 Debounce

首先防抖(Debounce)是什么:当事件频繁发生的时候,事件回调也频繁被调用,结果可能会造成页面卡顿,为了解决这个问题,我们为回调函数施加某种限制,使得当事件频繁发生时,回调函数不被调用,当事件停止时,一定间隔时间后才被调用。

这种场景比较常见,假设有一个需求:存在一个搜索框,当我们输入时,会有联想提示,提示内容需要从服务器请求。很简单,我们给input绑定onchange事件,事件回调中取得input内容,然后ajax请求数据,最后显示。但是如果用户一直输入内容,事件回调就会被一直调用,频繁的ajax请求和dom修改就会造成性能问题。

如何解决?

我们让回调函数只在用户输入结束时才被调用,于是我们就用到了防抖。

如何实现?

我们设置一个定时器,定时函数就是输入框的事件回调,每次输入框内容改变时,我们就清除上一个定时器,并重新设置定时器,这样当我们停止输入内容时,由于最后一次设置的定时器没有被清除,事件到达后,事件回调就会被调用。下面是实现代码。

/**
 * 防抖
 * @param {Function} fn 事件回调函数
 * @param {Number} delay 事件停止多久后函数被调用
 * @returns
 */
function debounce(fn, delay) {
  let timer;
  return function (...params) {
    timer && clearTimeout(timer);
    timer = setTimeout(() => {
      // 这里通过apply方法调用,防止this指向错误
      fn.apply(this, params);
    }, delay);
  };
}

/**
 * 事件回调
 * @param {Object} e
 */
function handleChange(e) {
  console.log(e);
}

input.onchange = debounce(handleChange);

节流(Throttle)

节流(Throttle)同样是为了处理函数被频繁调用造成的性能问题,但是和防抖(Debounce)不一样的是:防抖是固定间隔时间内只被调用一次,而且执行时间在时间间隔的开始。

这么解释比较抽象,我们假设一个场景:瀑布流布局里使用了absolute实现,根据窗口resize我们就需要重新计算布局,当页面上图片越多时,计算量就越大,当用户拉小了窗口尺寸,这一过程会触发多次resize事件,每一次事件回调都要计算新的布局位置,然后交给浏览器重绘,造成了明显的页面卡顿。

解决方法呢?我们限制不管用户持续多久的resize事件触发,回调函数只会每间隔500毫秒进行一次,比如说:我持续了5秒的拉伸或缩放窗口,事件回调只会执行10次。

实现方法呢?

我们定义一个变量last,让它存储最后一次触发事件的时间,当事件触发时,获得现在的时间,把两个时间进行比较,如果结果大于设置的间隔时间,我们才执行回调函数,并把现在时间赋值给last,供下一次对比。实现代码如下。

/**
 * 节流
 * @param {Function} fn
 * @param {Number} interval
 * @returns
 */
function throttle(fn, interval) {
  let last = 0;

  return function (...params) {
    const now = +new Date();
    if (now - last >= interval) {
      last = now;
      // 这里我们同样使用apply调用函数,传入this,防止this指向错误
      fn.apply(this, params);
    }
  };
}

/**
 * 事件回调
 * @param {Object} e
 */
function handleResize(e) {
  // .... 计算布局
}

window.onresize = throttle(handleResize, 500);

利用节流优化防抖

还是防抖的假设场景。如果用户一直输入内容,回调函数不会被执行,只有等用户结束了输入才会被调用。在这一个过程当中,用户可能会觉得页面卡住了,没有反应。

为了优化用户体验,我们可以利用节流的思想对防抖进行改进。

我们设置一个时间,用户在这个时间间隔内,防抖起作用,超过这个时间后,防抖就没有必要再等待用户停止输入了,我们先给他执行一次回调函数,以此可以给用户产生反馈,然后重新开始下一轮的防抖。最后实现的代码是这样的。

/**
 * 防抖
 * @param {Function} fn
 * @param {Number} interval
 * @returns
 */
function debounce(fn, delay, max = 1000) {
  let timer;
  let last = 0;
  return function (...params) {
    timer && clearTimeout(timer);
    const now = +new Date();
    if (now - last >= max) {
      last = now;
      fn.apply(this, params);
    } else {
      timer = setTimeout(() => {
        fn.apply(this, params);
      }, delay);
    }
  };
}

/**
 * 事件回调函数
 */
function log(e) {
  console.log('value change');
}

input.onchange = debounce(log, 1000, 2000);

总结

防抖(Debounce)和节流(Throttle)的实现都是为了优化性能问题,而且借助了闭包进行实现。总之区分两种优化方法就是:

Respond

There always be wrong words in life, so let it go.

*点击图片可关闭图片灯箱