防抖与节流是两个出于性能考虑提出的概念,如果在短时间内多次触发同一函数,可能会产生页面的卡顿,以及性能的降低,针对这种情况,提出了防抖与节流两个解决方案。

防抖与节流的概念

为了解释如上的两个概念,我们以一个例子开始。有一个搜索框,当用户输入搜索内容时,会同时向后端发起请求,给出相近的词条以给用户提示

image-20210429232147448

如果当每次用户输入一个字符都向都端发起一个请求,那么发起请求的次数就太频繁了,这时有两种策略:

  1. 当用户停止输入一段时间后,例如 500ms,我们向后端发起一个请求
  2. 每隔固定的时间向后端发起一个请求

第一种策略我们叫做防抖,第二种策略我们称为节流。

有一个形象的比喻,以公交车发车为例,有两种发车方案:

  1. 当乘客上车后,如果经过一段时间没有乘客上车了,就发车
  2. 发车时间固定,每隔一段时间发一次车

我们将发车比喻为向后端发起一个请求,那么这两种发车的策略就分别对应为防抖和节流。

防抖实现

我们要实现一个 debounce 函数,该函数接收一个函数以及时间,返回一个函数,debounce 函数的作用就是将一个普通的函数变为防抖的函数,即多次调用该函数,只有经过指定时间后,函数才会触发一次

const debounce = (fn, time) => {
let timerId

return function(...args) {
clearTimeout(timerId)
timerId = setTimeout(() => {
fn.apply(this, args)
}, time)
}
}

上面有几个注意事项:

  • timerId 要放在返回的函数之外,形成闭包,以确保每次调用函数时访问的是同一个 timerId

  • 返回的函数不要写成箭头函数的形式

    // 不要写成这样
    return (...args) => {

    }

    因为这样会丢失 this

  • 最后要通过 apply 方法将 this 传入

节流实现

我们要实现一个 throttle 函数,该函数的作用是将一个普通函数转化为节流函数,也接收两个参数,第一个参数为函数,第二个函数为时间,当多次调用该函数,在一段时间内只能触发一次

const throttle = (fn, time) => {
let pre = 0;

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

pre 表示上一次执行的时间,默认值为 0,这样第一次调用该函数时会立即执行该函数。每次执行函数时,判断距上次执行已经经过的时间 Date.now() - start 是否大于设定的时间,大于则执行,小于则不执行,以达到节流的目的,执行函数后,设置 pre 为当前函数执行时的时刻。

返回的函数不能写为箭头函数,同样是因为会丢失 this