浅谈函数的防抖和节流

1,011 阅读4分钟

防抖(debounce)

所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

通俗点来说,就是指用户频繁触发的时候,只执行一次事件(执行第一次或最后一次)【可以设置频繁的间隔时间】。

目的:频繁触发中只执行一次;

应用场景:按钮点击

模拟情景:当点击按钮的时候,期望500ms后输出hello,那么如果在500ms内疯狂点击多次,会发现最后输出多次hello;

那么如何在500ms内只触发一次事件,输出一次hello呢?

思路:设置间隔时间500ms,第一次点击后没有立即执行,等待500ms,观察500ms内是否触发了第二次,如果触发第二次就说明是频繁点击,不去执行函数,如果没有触发第二次则认为非频繁点击,此时去执行函数。依此类推观察第三次和第二次的间隔......

在间隔时间内,只要频繁点击了就清空上次定时器,重新计时。

function func(){
    console.log('hello');
}

btn.onclick = function(){
    ...
}

实现

前面提到了时间间隔,那么需要借助setTimeout函数以及柯理化函数来实现,具体过程:

/*
 *  debounce:实现函数的防抖(目的:频繁触发中只执行一次)
 *  @params
 *		func:需要执行的函数
 *		wait:检测防抖的间隔频率
 *		immediate:是否立即执行(true控制第一次触发时执行函数,默认false是以最后一次触发为准)
 *	@return
 *		返回一个可被调用执行的函数
 */
function debounce(func, wait = 500, immediate = false) {
 	let timer = null;
 	return function anonymous(...params) {
        //判断立即执行的条件,并非immediate=true就立即执行,需要判断此时是否设置了定时器
        //只有第一次执行,需要判断timer的值来区分是第一次还是第二次
  		let now = immediate && !timer; 
  		clearTimeout(timer);
  		timer = setTimeout(() => {
   			timer = null;
   			// 执行函数:注意保持THIS和参数的完整度
   			!immediate ? func.call(this, ...params) : null;
  		}, wait);
  		now ? func.call(this, ...params) : null;
 	};
}

最后调用我们实现的防抖函数测试效果,发现和预期效果一样:在500ms内多次点击仅执行一次事件,输出一次hello。

btn.onclick = debounce(func);

节流(throttle)

所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。

通俗点来说,节流会缩减频率。

目的:频繁触发中缩减频率;

应用场景:滚动条 window.onscroll / 输入框实时搜索 / onresize

模拟场景:页面中当滚动条滚动时,触发多次scroll事件,页面很长会执行几十次、几百次......

思路:如何缩减频率呢?可以设置一个执行间隔时间,比如500ms,也即是我们希望在指定时间间隔500ms内执行一次scroll事件,直到过了这个期限才重新生效,这样原本执行100次的可能只需要执行20次。

实现

/*
 * throttle:实现函数的节流(目的:频繁触发中缩减频率)
 * @params:
 *	 	func:需要执行的函数
 *   	wait:自己设定的间隔事件(频率)
 * @return
 * 		返回一个可被调用执行的函数
 */
function throttle(func, wait = 500) {
 	let timer = null,
  		previous = 0;  //记录上一次操作时间
 	return function anonymous(...params) {
  		let now = new Date(), //当前操作的时间
   			remaining = wait - (now - previous);   //剩余时间
  		if (remaining <= 0) {
  			// 两次间隔时间超过频率,把方法执行
   			clearTimeout(timer);
   			timer = null;
   			previous = now;
   			func.call(this, ...params);
  		} else if (!timer) {  //当前定时器不存在才设置,防止多次设置定时器
   			// 两次间隔时间没有超过频率,说明还没有达到触发标准,设置定时器等待(还差多久等多久)
   			timer = setTimeout(() => {
    			clearTimeout(timer);
    			timer = null;
    			previous = new Date();
    			func.call(this, ...params);
   			}, remaining);
  		}
 	};
}

防抖和节流的区别

防抖:防止抖动-----执行一次(次数) 节流:降低频率-----执行多次(频率)

一句话可以概括为:防抖是将多次执行变为第一次执行或最后一次执行,节流是将多次执行变为每隔一段时间执行。

相同点:防抖和节流都能有效减少浏览器引擎的损耗,防止出现页面堵塞卡顿现象。