防抖和节流

79 阅读2分钟

前言

防抖和节流是闭包的经典使用场景。要理解防抖和节流,必须先理解闭包。

防抖和节流一般在 resize 或 scroll 时使用,避免处理事件无限调用,影响用户体验。至于使用防抖还是节流根据业务需要。

防抖-debounce

事件触发后,只在最后执行一次。

function debounce(fn, wait) {
  let timer = null
  return function(...args) {
    // 使用了外部函数的变量timer,这是一个不会被销毁的闭包。
    // 事件触发过程中,该函数会不停的执行,一直给timer赋值,再clearTimeout
    // 直到最后一次触发,会有一个定时任务放入事件队列执行。
    if(timer) clearTimeout(timer)
    timer = setTimeout(() => {
        fn.apply(this, args)
    }, wait)
  }
}

// 处理函数 
function handle() { console.log(Math.random())} 

// 滚动事件 
window.addEventListener('scroll', debounce(handle, 1000));

节流-throttle

事件触发后,每隔一段时间,触发一次。

时间戳:

var throttle = function(func, delay) {
  var prev = Date.now();
  return function() {
      var context = this;
      var args = arguments;
      var now = Date.now();
      if (now - prev >= delay) {
          // 1. 这里使用了外部函数变量prev,是一个不会被销毁的闭包
          // 2. 事件执行过程中,prev的值不变,但now的值每次执行都会重新赋值,是会变的
          // 3. 当大于delay时,会执行一次func,再给prev重新赋值。
          // 4. prev重新赋值后,重复2、3过程,达到每隔一段时间执行一次。
          func.apply(context, args);
          prev = Date.now();
      }
  }
}

// 处理函数 
function handle() { console.log(Math.random())} 

// 滚动事件 
window.addEventListener('scroll', throttle(handle, 1000));

定时器实现:

var throttle = function(func, delay) {
  let timer = null
  return function() {
      var context = this;
      var args = arguments;
      if(!timer) {
        // 1. 使用了外部函数变量timer,是一个不会被销毁的闭包
        // 2. timer为空时,赋值一个定时器
        // 3. 等到定时器执行回调,func执行一次,timer赋值为空
        // 4. 下次事件触发,再给timer赋值一个定时器,达到每隔一段时间执行一次
        timer = setTimeout(function() {
          func.apply(context, args)
          timer = null
        }, delay)
      }
  }
}

时间戳 + 定时器:

大家自行理解哈,“时间戳 + 定时器” 的方式保证了事件最后一次触发后,会再执行一个定时器。

var throttle = function(func, delay) {
  let timer = null
  let startTime = Date.now()
  return function() {
      var context = this;
      var args = arguments;
      let curTime = Date.now()
      let remaining = delay - (curTime - startTime)
      clearTimeout(timer)
      if (remaining <= 0) {
        func.apply(context, args);
        startTime = Date.now();
      } else {
        timer = setTimeout(function() {
          func.apply(context, args)
          timer = null
        }, delay)
      }
  }
}

clearTimeout 都是在回调还没执行的时候将定时器清除的。

总结:

防抖和节流是个老生常谈的问题了,难点是要理解闭包的原理,这个就简单了。