前端学习笔记(十九)--防抖和节流

878 阅读3分钟

1. 防抖和节流

有时候我们想控制某个函数触发的频率,比如说某个鼠标移动事件的监听函数,我们并不想每次移动都触发该函数,那样太耗费性能了。
可以点进这个网页直观看到节流和防抖的动画效果。

1.1 防抖

1.1.1 防抖的含义

先说防抖,防抖可以给函数设置一个 timeout,等到 timeout 过后才执行。但是重要的是,如果在这个时间段里重新触发函数的话,timeout 就会重置。也就是说如果每次触发事件的频率很快的话且不停下来的话,函数永远都不会执行。只有当两次事件触发的间隔大于 timeout,函数才会执行。

1.1.2 为什么防抖要叫做防抖

为什么防抖要叫防抖呢?从 debounce 英文来看,可以直接翻译为"去掉反弹"。比如说丢一个乒乓球掉在地上,乒乓球会弹起->落下->弹起->...很多次。开始这可能没什么问题,但是往往当动能即将消耗完,乒乓球弹跳距离非常小的时候,乒乓球弹跳频率会相当快,你会听到 dadadadadada 的声音。反映到程序上就是高频率的事件触发。而使用了 debounce 之后,就像是把乒乓球快要停止弹跳前的那些高频率的弹跳整合成一次弹跳,也就是“去掉反弹”,这样你就听不到那恼人的 dadadada 的声音了。

1.1.3 防抖的实现

  1. lodash里 有对应的函数 _.debounce,名字就是防抖的英文。
  2. 也可以手动实现,参考上面那个动画网站的源码:
// 防抖函数,使用了函数闭包的原理
// @param func 想要防抖的函数
// @param delay 防抖延迟的时间
function debounce(func, delay) {

  let timer; // 在外部函数声明计时器,以便之后通用
  
  return function () {
    let _this = this; // 为 setTimeout 做准备
    let _args = arguments; // arguments 为每个函数都自带的实参的特殊变量。
                           // 因为 setTimeout 里的函数也有自己的 arguments,自然这个也要提前储存下来
    
    // 重置计时器,引用了外部函数的变量
    clearTimeout(timer);
    timer = setTimeout(function () {
      func.apply(_this, _args); // 因为 arguments 是类数组形式,所以用 apply
    }, delay);
  }
}

// 使用方式
debouncedMyFunc = debounce(myFunc, 250);
debouncedMyFunc(...) // 使用防抖后的 debouncedMyFunc 代替 myFunc。

1.2 节流

1.2.1 节流的含义

节流的和防抖的区别在于,防抖在高频率的触发下不会执行函数(结束时执行一次),而节流会。
节流的作用是给事件设置一个阈值,事件触发的频率不能快过这个阈值,如果快过了,就会被强制变成阈值的频率。

1.2.2 为什么节流要叫做节流

就像是拧水龙头,如果你拧到最大,流出的水太快了,你会拧回去一点让水变小一点。(相比之下,如果流速太大,此时防抖就会直接不让你流了)

1.2.3 节流的实现

  1. 同样,lodash 也提供了函数 _.throttle
  2. 以下是手动实现:
// 节流函数
function throttle(func, threshhold) {

  let timer; // 用于防抖
  let last; // 上次执行的时间,用于计算两次事件触发的时间差
  
  return function () {
  
    let now = new Date().getTime(); // 获取现在的时间戳
    let _this = this;
    let _args = arguments;
    
    if (last && (now - last) < threshhold) {
        // 先判断是不是第一次执行,如果不是再计算时间差
    	// 这段使用了防抖,可以保证最后一次的事件触发被执行,不加也行,但很多实现都加了
        clearTimeout(timer);
        timer = setTimeout(function () {
          func.apply(_this, _args);
        }, threshhold);
    } else {
      // 第一次执行,或者和上次执行时间超过了阈值,才会执行函数
      last = now; // 为下次做准备
      func.apply(_this, _args); // 立即执行函数
    }
  }
}

// 使用
myThrottle = throttle(myFunc, 250);
myThrottle(...)