Throttle & Debounce

As I was writing TOCX, I need to avoid infinitely invoke function. So It's necessary to know throttle and debounce, of course the difference between throttle and debounce.

Debounce

Debounce a function when you want it to execute only once after a defined interval of time. If the event occurs multiple times within the interval, the interval is reset each time.

Throttle

Throttle a function when you want it to execute periodically with an interval in between each execution.

differ

以电梯运行抽象比较:

throttle 策略的电梯:保证如果电梯第一个人进来后,15秒后准时运送一次,不等待。如果没有人,则待机。
debounce 策略的电梯:如果电梯里有人进来,等待15秒。如果又人进来,15秒等待重新计时,直到15秒超时,开始运送。

以css-tricks的例子演示它们的区分:

See the Pen The Difference Between Throttling, Debouncing, and Neither by LIUWEI (@lwxyfer) on CodePen.

还有一个可视化的演示: http://demo.nimius.net/debounce_throttle/

代码实现

理解并且区分throttle和debounce是很容易的,关键在于代码的实现。但是很多网上的文章真的搞反了。。。

Debounce实现

一个简单的实现:

/**
 * @param fn {Function}   实际要执行的函数
 * @param delay {Number}  延迟时间,单位是毫秒(ms)
 * @return {Function}     返回一个“防反跳”了的函数
 */
function debounce(fn, delay) {  
  // 初始定时器
  var timer = undefined

  return function () {
    // 保存函数调用时的上下文和参数,传递给 fn
    var context = this
    var args = arguments
    // 重复调用,就清除定时器
    if(timer) clearTimeout(timer)

    timer = setTimeout(function () {
      fn.apply(context, args)
    }, delay)
  }
}

throttle

/**
* @param fn {Function}   实际要执行的函数
* @param delay {Number}  执行间隔,单位是毫秒(ms)
* @return {Function}     返回一个“节流”函数
*/
function throttle(fn, threshhold) {  
  // 记录上次执行的时间
  var last
  // 定时器
  var timer
  // 默认间隔为 250ms
  threshhold || (threshhold = 250)
  // 返回的函数,每过 threshhold 毫秒就执行一次 fn 函数
  return function () {
    // 保存函数调用时的上下文和参数,传递给 fn
    var context = this
    var args = arguments
    var now = +new Date() // Date.now()
    // 如果距离上次执行 fn 函数的时间小于 threshhold,那么就放弃
    // 执行 fn,并重新计时
    if (last && now < last + threshhold) {
      clearTimeout(timer)

      // 保证在当前时间区间结束后,再执行一次 fn
      timer = setTimeout(function () {
        last = now
        fn.apply(context, args)
      }, threshhold)
    // 在时间区间的最开始和到达指定间隔的时候执行一次 fn
    } else {
      last = now
      fn.apply(context, args)
    }
  }
}

执行机制

setTimeout和setInterval的运行机制是,将指定的代码移出本次执行,等到下一轮Event Loop时,再检查是否到了指定时间。如果到了,就执行对应的代码;如果不到,就等到再下一轮Event Loop时重新判断。这意味着,setTimeout指定的代码,必须等到本次执行的所有代码都执行完,才会执行。

每一轮Event Loop时,都会将“任务队列”中需要执行的任务,一次执行完。setTimeout和setInterval都是把任务添加到“任务队列”的尾部。

所以这里需要对JS整个的运行机制,异步处理需要深入了解。

以一下代码为例:

function x() {  
setTimeout(function () {  
console.log(Date.now())  
  console.log('timeout!永远最后');
}, 0);
for(let i =0;i<10;i++){  
console.log(Date.now())  
}
console.log('ddd',Date.now())  
}
x();  
(function(){
console.log('我在最后')})()  

reference: