JavaScript:理解函数的防抖和节流


定义

防抖:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。

节流:每隔一段时间,只执行一次函数。

防抖和节流图解:http://demo.nimius.net/debounce_throttle/

作用

解决性能问题:在浏览器中,有一些事件(滚动,窗口大小变化)触发的频率过高;对于用户,用户可能疯狂点击某个按钮或者在输入框里面疯狂输入(可能网太慢很着急或者就是爱点);而每一次变化、点击和输入都触发事件,产生了资源被浪费或者到溢栈等一些导致用户体验变差或者功能直接死掉的情况。

防抖和节流通过控制这些事件在一段时间内调用的频率,从而使资源合理分配和少牺牲用户体验。

实现

1.防抖- debounce

定义:防抖是在事件触发 n 秒后在执行回调函数,如果在这 n 秒内又被触发,重新计时。

触发 n 秒后,首先需要一个计时器,用来让 n 秒后触发执行;

执行回调函数,这时候需要把传入的参数给到回调函数;

执行完回调函数,需要返回回调函数的结果;

防抖是给一个函数增加一个功能,防抖功能,当目标函数需要的时候运行触发。

所以防抖返回的是一个增加了新功能的函数。

// 基础版本
function debounce(fn, delay) {
  let timer; // 计时器
	const debounceFn = function () { // 加上了功能的函数
		const context = this; // 存储当前 this 执行
    if (timer) clearTimeout(timer); // n 秒内重新触发,清除计时器归零
    timer = setTimeout(() => { // n 秒后执行
	    return fn.apply(context, arguments); // 执行回调函数
    }, delay)
  }

  return debounceFn; // 返回加了功能的函数
}

有了基础版本,可以在上面进行优化并提供更多功能,如:
增加取消功能:

// 取消功能,我们返回的是函数变量,怎么办?那就在返回的函数变量上新增一个方法取消
function debounce(fn, delay) {
  let timer; // 计时器
  const debounceFn = function () { // 加上了功能的函数
  const context = this;
    if (timer) clearTimeout(timer); // n 秒内重新触发,清除计时器归零
    timer = setTimeout(() => { // n 秒后执行
      return fn.apply(context, arguments); // 执行回调函数
    }, delay);
  }

  debounceFn.cancel = function() {
    clearInterval(timer);
    timer = null;
  }

  return debounceFn;
}

还可以增加更多功能,如

增加立即调用功能 flush
...
这里加个功能:鼠标一进入就显示数字
代码如下:

    function debounce(fn,delay,immediate){
            var timeout
             return function(){
                const self = this
                 if(timeout) clearTimeout(timeout)
                 if(immediate){
                    var callback = !timeout
                    timeout = setTimeout(function(){
                        timeout = null
                    },delay)
                    if(callback)  fn.apply(self,arguments)
                 }else{
                    timeout = setTimeout(function(){
                    fn.apply(self,arguments)
                },delay)
                 }      
             }
        }

2.节流 - throttle

定义:每隔一段时间,只执行一次函数。

重要的是在一个事件频繁触发的情况下,固定一段事件间隔执行一次函数;

给一个函数事件新增一个节流的功能,我们的函数签名输入是回调函数和间隔时间,输出为给回调函数赋予了节流功能的新函数;

实现思路:当触发事件的时候,我们设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,直到定时器执行,然后执行函数,清空定时器,这样就可以设置下个定时器。

function throttle(fn, interval) {
  let timer;
  const throttleFn = function() {
    const context = this;
    let result;
    if (!timer) {
      timer = setTimeout(() => {
        timer = null;
        result = fn.apply(context, arguments)
      })
    }

    return result;
  }
}

可增加更多的功能:

取消功能,同防抖一样;
...

使用场景

不用死记硬背场景,理解了概念和几个实例场景之后会在脑子留下了一个钩子,当自己重新进入相应的场景会唤起自己的大脑,会想到是不是可以做一个这样的优化。以下为一些常见的使用实例场景:

1.防抖

  • Input输入的搜索或验证(输入后需要进行后台搜索或验证)
    2.节流
  • DOM 元素的拖拽功能实现(mousemove)
  • 射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)
  • 计算鼠标移动的距离(mousemove)
  • Canvas 模拟画板功能(mousemove)
  • 搜索联想(keyup)

注意

使用防抖的时候避免绑定错误

// 错误
$(window).on('scroll', function() {
   _.debounce(doSomething, 300); 
});
// 正确
$(window).on('scroll', _.debounce(doSomething, 200));

转自:https://github.com/random-xiang/issue-blog/issues/14