有了防抖与节流,领导再也不挠我的头皮

144 阅读2分钟

防抖

防抖的定义

防抖在事件被触发n秒后在执行回调,如果在这n秒内又被触发,则重新计时。 大白话可以这样理解:你尽管触发事件,但是我一定在事件触发 n 秒后才执行,如果你在一个事件触发的 n 秒内又触发了这个事件,那我就以新的事件的时间为准,n 秒后才执行,总之,就是要等你触发完事件 n 秒内不再触发事件,我才执行,真是任性呐!

来个使用场景

<!DOCTYPE html>
<html lang="zh-cmn-Hans">

<head>
  <meta charset="utf-8">
  <meta http-equiv="x-ua-compatible" content="IE=edge, chrome=1">
  <title>debounce</title>
  <style>
    #container {
      width: 100%;
      height: 200px;
      line-height: 200px;
      text-align: center;
      color: #fff;
      background-color: #444;
      font-size: 30px;
    }
  </style>
</head>

<body>
  <div id="container"></div>
  <script>
    var count = 1;
    var container = document.getElementById('container');

    function getUserAction() {
      container.innerHTML = count++;
    };

    container.onmousemove = getUserAction;
  </script>
</body>

</html>

a.png 每在上图中黑色区域移动鼠标一次,中间的数字+1。 因为这个例子很简单,所以浏览器完全反应的过来,可是如果是复杂的回调函数或是 ajax 请求呢?假设 1 秒触发了 60 次,每个回调就必须在 1000 / 60 = 16.67ms 内完成,否则就会有卡顿出现。

为了解决这个问题,我们可以通过防抖。

防抖最初版

function debounce(fn,deplay) {
  let timer = null
  return function () {
    if (timer) { clearTimeout(timer) }
      timer = setTimeOut(func,deplay)
  }
}

如果我们要使用它,以最一开始的例子为例:

container.onmousemove = debounce(getUserAction, 1000);

现在随你怎么移动,反正你移动完 1000ms 内不再触发,我才执行事件。

存在的问题:

防抖第二版

1.this指向不对 原本this指向的是container元素,现在加上debounce后,this指向就变为window,这可不行,来解决一下这个问题吧。

// 第二版
function debounce(func, wait) {
    var timeout;
    return function () {
        var context = this;
        clearTimeout(timeout)
        timeout = setTimeout(function(){
            func.apply(context)
        }, wait);
    }
}

第2个问题: JavaScript 在事件处理函数中会提供事件对象 event,我们修改下 getUserAction 函数:

function getUserAction(e) {
    console.log(e);
    container.innerHTML = count++;
};

防抖第三版

如果我们不使用 debouce 函数,这里会打印 MouseEvent 对象。 但是在我们实现的 debounce 函数中,却只会打印 undefined! 所以我们再修改一下代码:

// 第三版
function debounce(func, wait) {
    var timeout;
    var args =a rguments
    return function () {
        var context = this;
        clearTimeout(timeout)
        timeout = setTimeout(function(){
            func.apply(context, args)
        }, wait);
    }
}

防抖最终版

最后用ES6来改写一下吧。

// 第四版
function debounce(fn, apply) {
  let timer;
  return function (...args) {
    if (timer) clearTimeout(timer);
    let context = this;
    timer = setTimeout(function () {
      fn.apply(context, args);
    });
  };
}

节流

节流的定义

如果你持续触发事件,每隔一段时间,只执行一次事件。d

节流最初版

使用时间戳,当触发事件的时候,我们取出当前的时间戳,然后减去之前的时间戳(最一开始值设为 0 ),如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行。

// 第一版
function throttle(func, wait) {
    var context, args;
    var previous = 0;

    return function() {
        var now = +new Date();
        context = this;
        args = arguments;
        if (now - previous > wait) {
            func.apply(context, args);
            previous = now;
        }
    }
}

节流最终版

ES6改写一下

function throttle(fn, deplay) {
  let previous = 0;

  return function (...args) {
    let now = Date.now();
    let context = this;
    if (now - previous > deplay) {
      fn.apply(context, args);
      previous = now;
    }
  };
}