防抖and节流and区别 | 青训营笔记

102 阅读3分钟

Girl.jpg

这是我参与「第四届青训营 」笔记创作活动的第1天

第一次发布笔记,欢迎指正

一、防抖(防止手抖)

场景

现在我们有一个通过监听输入框实时修改名称的需求,如下图

1213309-20220427154206856-1634147404.gif

主要问题

修改input的值,对应span标签内的文本就会被修改,但假设我们此时的修改并不是一次innerHTML的更新,而是要将最新的name同步给后端,那么这里就需要发起一次后端请求。抛开请求耗时不说,假设用户在一瞬间增增删删了十余次,此刻你就得发送十多次请求,抛开性能消耗,万一用户网络不好,你甚至还会遇到因调度问题导致数据展示后发先至的bug

解决

当事件被触发的时候,设定一个容错时间延迟执行动作(计时器),若这期间再次被触发,则重新设定周期(清除上一次计时器,重新定时),直到容错时间结束,才触发事件执行动作。代码如下

// 定义防抖函数
const debounce = function (fn, wait) {
  // 自由变量,debounce执行完成被释放,time也不会被释放
  let time;
  // 返回一个闭包
  return function () {
    // 清除上一次的定时器
    if (time) {
      clearTimeout(time);
    };
    // wait时间后执行
    time = setTimeout(fn, wait);
  }
};

// 

const span = document.querySelector('.name');
const input = document.querySelector('.input');

const changeName = function () {
  console.log(this);
  span.innerHTML = input.value;
};


const changeName_ = debounce(changeName, 3000);

input.onkeyup = changeName_;

附加问题

我们想要这个changeName方法更通用一些,需要让他能够处理参数,我们知道事件可以通过event获取到当前操作的对象target,我们修改changeName为:

const changeName = function (e) {
  console.log(this)
  span.innerHTML = e.target.value;
};

对应的,我们还要修改防抖代码,实现e的接收

// 定义防抖函数
const debounce = function (fn, wait) {
  // 自由变量,debounce执行完成被释放,time也不会被释放
  let time;
  // 返回一个闭包,接受参数
  return function (...args) {
    // 清除上一次的定时器
    if (time) {
      clearTimeout(time);
    };
    // 不再是直接执行fn,在内部传递参数
    time = setTimeout(function () {
      fn(...args);
    }, wait);
  }
};

虽然已经可以用但是有一个细节被忽略,就是this的指向问题,定时器执行后,你会发现changeNamethis指向window(非严格模式)

于是再改

const debounce = function (fn, wait) {
  // 自由变量,debounce执行完成被释放,time也不会被释放
  let time;
  // 返回一个闭包,接受参数
  return function (...args) {
    // 保存闭包被调用时的this
    const this_ = this;
    // 清除上一次的定时器
    if (time) {
      clearTimeout(time);
    };
    // 不再是直接执行fn,在内部传递参数
    time = setTimeout(function () {
      // 通过apply修改fn的this
      fn.apply(this_, args);
    }, wait);
  }
};

二、节流(节省流量)

场景

现在有一个下图的功能展示

1213309-20220501214612645-480574599.gif

主要问题

滚动的触发是高频的,假如有一个类似这个滚动效果的高频触发事件,我们想要做到的是,让这个触发频率降低。

解决

也就是在固定一段时间内,只执行一次函数,若这段时间有新事件触发,则选择不执行。当这段时间结束后,又有事件触发,才再次开始一段「保护期」。代码如下:

const throttle = (fn, wait) => {
  let time;
  return function (...args) {
    const this_ = this;
    // 如果存在time则不创建新的定时器
    if (!time) {
      time = setTimeout(() => {
        // 定时器会在wait后执行,执行完毕清空time
        time = null;
        fn.apply(this_, args);
      }, wait);
    }
  }
}

三、区别

简单的理解例子,假如现有一个永不终止的高频事件

对于防抖,它会不断设置新的定时器,也就是我们一直在疯狂触发,但是永远都得不到这个触发的反馈结果

对于节流,同样的前提和操作之下,我们隔一段时间会得到一次反馈结果。

详细解释-推荐原文1原文2