码字,杂谈

手动实现JS节流

什么是节流

在函数调用过程中,避免过于频繁的调用,而是间隔某一时间后调用一次的方法,叫做节流。

节流做什么

节流可以有效避免短时间内大量调用某一方法或数据,保证页面的稳定性和数据的准确性。

一个小的例子

使用 underscore 的节流功能来测试一下效果。

中文网址

在页面中直接导入 cdn 即可。

https://cdn.bootcss.com/underscore.js/1.9.1/underscore.js

未节流时的样子

将下面内容粘贴到一个 HTML 的 body 标签中。

<div
  id="container"
  style="width:100%;height:200px;line-height:200px;text-align:center;color:#fff;background-color:#444;font-size:30px;"
></div>
<script>
  let count = 0;
  let container = document.querySelector("#container");

  // 此处为高频调用函数
  function doSomething() {
    container.innerHTML = count++;
  }

  container.onmousemove = doSomething;
</script>

这段代码会生成一个灰色框,只要鼠标在其内部移动,就会调用 doSomething 函数,导致数字不断增长。

使用节流

同样地,将下面内容粘贴到一个新的 HTML 的 body 标签中。

<div
  id="container"
  style="width:100%;height:200px;line-height:200px;text-align:center;color:#fff;background-color:#444;font-size:30px;"
></div>
<script src="https://cdn.bootcss.com/underscore.js/1.9.1/underscore.js"></script>
<script>
  let count = 0;
  let container = document.querySelector("#container");

  // 此处为高频调用函数
  function doSomething() {
    container.innerHTML = count++;
  }

  container.onmousemove = _.throttle(doSomething, 300);
</script>

这里导入了 underscore 库,这个库会导出一个 _ 的对象,它包含一个 throttle 方法,该方法的作用就是节流。第一个参数是函数原型,第二个参数是响应时间,这里我们设置 300ms 后响应。

另外还可以设置是否第一次首先执行和最后一次是否执行,传入: {leading: false}{trailing: false} 。当然,你也可以同时传入,但是需要注意,如果同时传入 false 的话,会出现 bug,该 bug 会导致下次启动时不响应 {leading: false} ,而是会立即执行第一次,这一点需要注意。

这次运行后,可以发现当鼠标移动时,不再一味地增加,而是每隔一段时间后(300ms)才会响应一次。

手动实现节流函数

利用时间戳方式

这样的方式会触发第一次,而不会触发最后一次。

function throttle(func, wait) {
  let context, args;
  let old = 0;
  return function () {
    context = this;
    args = arguments;
    let now = Date.now();

    if (now - old > wait) {
      // 立即执行
      func.apply(context, args);
      old = now;
    }
  };
}

利用定时器方式

这样的方式会触发最后一次,而不会触发第一次。

function throttle(func, wait) {
  let context, args, timeout;

  return function () {
    context = this;
    args = arguments;

    // 设置一个定时器,只有为空时才会触发,每次执行后都会重新设定一个定时器。
    if (!timeout) {
      timeout = setTimeout(() => {
        timeout = null;
        func.apply(context, args);
      }, wait);
    }
  };
}

合并为最终的完整本版

利用上面的两种实现方式,可以得到一个比较完整的版本。

function throttle(func, wait, options) {
  let context, args, timeout, result;
  let previous = 0;

  if (!options) options = {};

  let later = function () {
    // 这里控制再次出发时,第一次是否执行,当为双false时,也是这里会出现的问题。
    previous = options.leading === false ? 0 : Date.now();
    timeout = null;
    result = func.apply(context, args);
    if (!timeout) context = args = null;
  };

  var throttled = function () {
    context = this;
    args = arguments;

    let now = Date.now();

    // 第一次不执行,调整previous的值即可。
    if (!previous && options.leading === false) previous = now;

    if (now - previous > wait) {
      if (timeout) {
        clearTimeout(timeout);
        timeout = null;
      }
      // 立即执行
      previous = now;
      result = func.apply(context, args);
      if (!timeout) context = args = null;
    } else if (!timeout && options.trailing !== false) {
      // 设置一个定时器,只有为空时才会触发,每次执行后都会重新设定一个定时器。
      timeout = setTimeout(later, wait);
    }

    return result;
  };

  throttled.cancel = function() {
      clearTimeout(timeout);
      timeout = null;
      timeout = context = args = null;
  }

  return throttled;
}

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注