防抖与节流

函数节流

函数节流就是在指定时间间隔内只执行一次该函数

举个栗子:滚动条滚动事件

window.addEventListener(
  "scroll",
  function() {
    console.log("页面滚动了");
  },
  false
);

该代码的初衷是:用户每滑动一次滚动条,执行一次console.log('页面滚动了'),但事实是:

1.jpg

用户只滑动了一次滚动条,就多次触发了scroll事件,因为浏览器每隔一个很短的时间间隔就要判断滚动条的变化,如果执行函数内需要执行 DOM 操作,将会非常消耗资源,这个时候节流函数就派上用场了:

window.addEventListener("scroll", func(putOut, 400), false);

function func(method, waitTime) {
  let run = true;
  return function() {
    if (!run) return;
    run = false;
    setTimeout(() => {
      method.apply(this, arguments);
      run = true;
    }, waitTime);
  };
}

function putOut() {
  console.log("页面滚动了");
}

putOut函数是我们触发scroll事件希望执行的函数,而func是实际执行的函数,用来控制putOut函数执行次数,在func中声明了一个Boolean类型的变量run,然后返回一个函数,先判断!run值是否为true(run 是否为 false),条件成立则直接return退出函数,否则执行setTimeout,也就是在waitTime毫秒之后执行延时器内的命令,直到waitTime毫秒之后,才会再次执行函数

在这里putOut是可以直接被调用的,但是在实际情况中往往不会像现在一样只有一个console语句,可能会有很多的 DOM 操作,也可能会涉及到this指向问题,为了保证函数this指向正确,采用 apply 的方式执行函数putOut

2.jpg

效果非常明显,由于我们设置了节流函数,所以执行函数的次数大大减少,效率大大提高

防抖函数

防抖函数是指行为被触发的间隔超过指定间隔的时候,才会执行该函数
一个很经典的例子就是输入框的监听:
在用户注册的时候,可能需要检测用户的用户名是否重复,有两种方案:

  • 输入框失去焦点的时候触发检测事件
  • 输入框内容改变的时候触发检测事件

很多网站都会选择第二种方案,因为第一种的用户体验不太好,每次都要点一下其他地方才能知道自己的用户名有没有重复

let input = document.getElementById("input");
input.addEventListener("input", function() {
  console.log(this.value);
});

3.jpg

这样就实现了第二种方案(AJAX 用 console.log 代替),效果是有了,但是我们想要的是用户输入完整个用户名之后在开始检测,而事实是每当用户输入的时候就会进行检测,这样是相当浪费资源的行为,如果我把console.log替换成AJAX请求,那么我在输入完123456789的时候已经做了 9 次AJAX请求,这个时候防抖函数就派上用场了

let input = document.getElementById("input");
input.addEventListener("input", func(putOut, 400));
function func(method, waitTime) {
  let timer = null;
  return function() {
    clearTimeout(timer);
    timer = setTimeout(() => {
      method.apply(this, arguments);
    }, waitTime);
  };
}
function putOut() {
  console.log(this.value);
}

4.jpg

其实和函数节流十分相似,用户触发input事件之后执行func函数,函数内声明了一个变量timer,接着返回一个闭包函数,因为用户在waitTime毫秒之内又触发了input事件,所以清除先前的延时器,重新开始计时

这就实现了函数防抖功能,在用户输入完成之后waitTime毫秒之内不再有任何字符输入,就会执行putOut函数

总结

函数节流和函数防抖对提升性能都起到了非常大的作用,都用到了延时器,并且都用到了闭包函数来获取函数func内声明的属性的状态,以前耿直的我一直选择触发行为直接执行方法的方式,现在才知道是多么耗费资源