# 防抖与节流

函数防抖和函数节流:优化高频率执行js代码的一种手段,js中的一些事件如浏览器的resize、scroll,鼠标的mousemove、mouseover,input输入框的keypress等事件在触发时,会不断地调用绑定在事件上的回调函数,极大地浪费资源,降低前端性能。为了优化体验,需要对这类事件进行调用次数的限制。

# 防抖(debounce)

当持续触发事件时,一定时间内没有再触发事件,事件处理函数才会执行一次,如果设定的时间内触发了事件,就再重新开始延时。

应用场景:

  • 搜索框输入框连续输入进行AJAX验证时,只有当一定时间内没有继续输入才开始查询,如果一段时间内又有了输入就重新 开始计时,防止输入一个字符就去查询一次,频繁请求,浪费性能。

  • 监听scroll滚动事件,当滚动条一定时间内不再滚动了再执行事件。

  • 监听resize窗口改变来调整样式时,用户不断调整窗口大小,当停下来时,再确定窗口大小改变布局。

  • 拖拽dom功能move时,停止move时再执行某个事件。

实现:

利用 setTimeout函数来进行倒计时来做限定,每触发一次debounce事件,就执行一次setTimeout倒计时, 如果上一次倒计时没执行完又触发了debounce事件,就清除上一次的timer,然后重新倒计时。 直到最后一次限定时间后执行handle函数。

简易版:

var timer;
function debounce() {
  if (timer) clearTimeout(timer);
  timer = setTimeout(handle, 1000);
}
function handle() {
  console.log(1);
}

window.addEventListener('scroll', debounce);

封装一下:把timer拿到函数内,通过闭包return一个函数,这个函数可以访问timer,这样每次执行的都是这个返回的函数,但是也能访问到公共的timer。

function debounce(fn, wait) {
  var timer;
  return function() {
    if (timer) clearTimeout(timer);
    timer = setTimeout(fn, wait);
  }
}
function handle() {
  console.log(1);
}
window.addEventListener('scroll', debounce(handle, 1000));

最终版: 可以传入参数

先把debounce函数return的函数赋值给debounceCallbackdebounceCallback保留着对debounce的访问权,然后又能执行自己的操作。然后把参数传给debounceCallback,这样args就是通过赋值后return返回的函数debounceCallback传入的参数。然后再通过过apply传入参数借用执行debounce传入的fn函数。

function debounce(fn, wait) {
  var timer;
  return function () {
    var _this = this;
    var args = arguments; // 获取debounceCallback传入的参数
    if (timer) clearTimeout(timer);
    timer = setTimeout(function(){
      fn.apply(_this, args); // 把参数传给fn
    }, wait);

    // timer = setTimeout(() => { fn(args) }, wait); // 使用箭头函数就不需要apply
  }
}
function handle(arg) {
  console.log(arg);
}

var debounceCallback = debounce(handle, 1000); // 通过此步赋值得到return的函数,然后把参数传入 巧妙之处

window.addEventListener('scroll', function(e) {
  debounceCallback(e) // 传入参数
});

# 节流(throttle)

当持续触发事件时,让事件在一定的时间段内只执行一次函数。隔一段时间执行一次,减少执行的次数。但执行函数也是连续触发的, 只是频率变低。

应用场景:

  • 滚动无限加载时,用户滚动页面,每隔一定时间加载一次数据。

  • 输入框联想,用户一直输入,根据用户输入不停的提示联想词,这样使用节流,等到结束输入一定时间后,再去请求数据,这是防抖。

  • 用户点击按钮,一直不停的点击,使用节流减少提交次数。

实现:

可以通过时间戳和定时器两种方法实现

1、通过时间戳节流

throttle事件触发,开始时记录时间戳,等到下一次执行throttle事件计算时间差,如果差值达到就执行一次函数。

简易版:

var start = 0;
function throttle() {
  var end = Date.now();
  if (end-start > 1000) {
    console.log(1)
    start = end
  }
}

window.addEventListener('scroll', throttle);

封装版:

function throttle(fn, delay){
  var start = Date.now();
  return function(){
    var _this = this;
    var args = arguments;
    var end = Date.now();
    if(end - start > delay){
      fn.apply(_this, args);
      start = end;
    }
  }
}

function handle(arg) {
  console.log(arg);
}

var throttleCallback = throttle(handle, 1000)

window.addEventListener('scroll', function(e) {
  throttleCallback(e)
});

2、通过定时器节流

当throttle事件一直触发时,设置一个定时器,如果定时器存在就不执行handle函数,直到定时器结束,执行一次handle后清除定时器,然后再设置一次定时器,一定时间内只执行一次handle。

封装版:

function throttle(fn, delay){
  var timer;
  return function(){
    var _this = this;
    var args = arguments;
    if(!timer) {
      timer = setTimeout(function() {
        fn.apply(_this, args);
        timer = null
      }, delay)
    }
  }
}

function handle(arg) {
  console.log(arg);
}

var throttleCallback = throttle(handle, 1000)

window.addEventListener('scroll', function(e) {
  throttleCallback(e)
});

防抖是有定时器就不执行;节流是你没定时器我就执行。

TIP

某些情况下我们可以用 requestAnimationFrame 这个API代替节流,相当于 throttle(handle, 16)