前端日常一问:介绍一下防抖和节流的原理,以及如何区分?日常什么场景下会使用?使用代码实现?

前言

提到性能优化的时候,经常会说使用防抖和节流。那何为防抖和节流呢?为什么使用他们就能性能优化呢?今天我们来具体说明一下。

防抖(debounce)

原理
在规定时间内才能执行一次,如果在规定时间内又被触发,就清除计时,重新开始。
举例来说,就像我们日常坐地铁,每次到站后,列车员都会出站等待观察,等最后一个人进入后会等待十几秒,这十几秒内如果无人再进入就会关门,有的话会计时清零,重新等待,直至关门。

适用场景

  • 输入验证性质的input框
  • 搜索类input
  • 提交型按钮的点击事件(防重点击)

实现方式
我们采用定时器的方式,函数第一次执行时设置一个定时器,设定好时间,之后再调用就清除之前设置的定时器,重新设定一个新的定时器,没有存在没有被清除的定时器,在定时器计时结束后调用函数执行。
简易版代码实现如下:

function debounce(fn,wait=50) {
	let timer;
	return function(...arguments) {
		if(timer){
			clearTimeout(timer)
		}
		timer = setTimeout(()=>{
			fn.apply(this,arguments)
		},wait)
	}
}

节流(throttle)

原理
在规定时间内必须执行一次,并且在此时间内无视后来产生的函数调用请求。
如同我们现在手机上使用的番茄软件,设定一个小时内不看手机,在这一个小时内再打开手机都会有个提示,直至一个小时结束才可以打开。

适用场景
节流适用于操作更加频繁触发的事件中,比如onresize、mousemove等事件

实现方式
节流的实现方式其实有两种;

  1. 使用时间戳实现
    一开始时间设置为0,当调用事件时,先取出当前时间戳,减去之前的时间戳,如果结果大于设置的时间周期,就执行函数。然后更新时间戳为当前时间戳,如果小于就不执行。
    具体代码实现如下
    function throttle(func, wait=50) {
      let context, args;
      let previous = 0;
      return function () {
    	let now = +new Date();
    	context = this;
    	args = arguments;
    	if (now - previous > wait) {
    	  func.apply(context, args);
    	  previous = now;
    	}
      }
    }
    
  2. 使用定时器实现
    设置一个定时器,当触发事件时,如果有定时器存在就不执行,直至定时器执行在调用函数,清空定时器,设置下一个定时器
    具体代码实现如下
    function throttle(fn, wait=50) {
      let timeout;
      return function () {
    	const context = this;
    	const args = arguments;
    	if (!timeout) {
    	  timeout = setTimeout(function () {
    		timeout = null;
    		fn.apply(context, args)
    	  }, wait)
    	}
      }
    }
    

防抖和节流的区别

由此,我们可以看出
防抖取决于最后一次,节流取决于第一次;
防抖是无论前面执行多少次,只要有新的调用发生,之前的定时器就会清除,只执行最后一次也就是最新发生的调用,
节流则是无论后面发生的多少次触发,只要一开始执行了,我就在规定的时间内执行完成,后续的都不执行,直至计时结束后,重新开始。

实际上,我们可以用throttle来优化debounce,实现一个加强版的debounce。总不能一直等待,一直重新开始吧,总归要有个最终的时间截止。

function throttle(fn, delay=50) {
  // last为上一次触发回调的时间, timer是定时器
  let last = 0, timer = null
  return function () {
    let context = this
    let args = arguments
    let now = +new Date()
    // 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
    if (now - last < delay) {
    // 如果时间间隔小于我们设定的时间间隔阈值,则为本次触发操作设立一个新的定时器
       clearTimeout(timer)
       timer = setTimeout(function () {
          last = now
          fn.apply(context, args)
        }, delay)
    } else {
        // 如果时间间隔超出了我们设定的时间间隔阈值,那就不等了,无论如何要反馈给用户一次响应
        last = now
        fn.apply(context, args)
    }
  }
}

参考

前端性能优化原理与实践
木易杨前端进阶

posted @ 2021-08-23 15:05  卖萌实习生  阅读(254)  评论(0)    收藏  举报