js基础之函数的节流与防抖

函数节流和防抖的基本思想及应用场景

函数节流 (throttle)

基本思想:当一个事件在短时间内频繁触发时,通过超时调用来降低回调函数的执行频率。
代表性场景:高触发频率事件的降频。比如下面mousemove事件的监听:

function doSomething(){
	//执行一些操作
	...
}
document.addEventListener('mousemove', doSomething);

上面的代码为文档对象绑定了一个监听鼠标移动的事件。当鼠标在页面上连续移动的时候,监听器会频繁监听到这个事件(监听频率可能达到每秒几十次),并执行回调函数doSomething。这样,当鼠标在页面上连续移动的时候,js引擎每秒需要执行doSomething函数几十次,这显然会影响到网页的性能。如果页面上有很多类似的监听器,那网页非常容易卡死。

对于这种情况,我们就需要通过函数节流,来降低函数的执行频率,提升网页性能。

函数防抖(debounce)

基本思想:当一个事件在短时间内连续触发,且触发间隔小于设定的阈值时,暂时不执行回调函数,只在触发间隔超过一定的时间后才执行回调函数。
代表性场景:连续操作的延迟触发。比如现在用户正在填写邮箱,我们需要在用户输入的过程中检测邮箱格式是否正确,我们可能会写以下代码:

<input id="input"></input>
<span id="error"></span>
function validateEmail( email ){
	//通过正则表达式验证邮箱格式是否正确,这里暂不实现
	...
}
$("#input").on("input", function(){
	//如果格式不正确,就给出错误提示
	if( !validateEmail( $(this).val() ) ){
		$("#error").text("您的邮箱格式不正确!");
	} else {
		$("#error").text("");
	}
})

对于上述代码,每次用户输入新的字符,我们都会进行一次格式检查(如果输入速度特别快,受限于浏览器的响应速度,监听器可能无法每个字符的输入都监听到)。

显然,当用户在连续输入的时候,我们并不需要在每次输入时都进行检查(在没有输入完毕时一直提示用户邮箱格式错误是很讨厌的,我们不能让用户觉得讨厌!)。于是我们可以这么做:每次用户输入新字符后,我们就稍微等待一下(如2秒),如果用户没有继续输入新内容,我们就进行格式检查,否则就什么也不做。这样只要用户停止输入一段时间,我们就会告知用户邮箱格式是否正确,用户体验就会大大提升!

可能有人会问,为什么这里不绑定输入框的change事件?那是因为change事件只在输入框失去焦点后才会触发,我们不想让用户需要点一下其他地方才能知道邮箱格式是否正确。

实现原理

函数节流 (throttle)

我们通过一个外部变量作为“锁”,来控制当前回调函数是否可以执行,并用一个定时器来控制锁的打开和关闭。代码如下:

//设置“锁”,用来控制是否执行回调操作,初始时允许执行
var canRun = true;
document.addEventListener('mousemove', function(e){
	//如果当前不允许执行回调函数,直接返回
	if( !canRun ){
		return;
	}
	//把锁给锁上
	canRun = false;
	//我们输出当前鼠标的位置,来验证节流后事件的执行频率被降低
	console.log(e.x);
	//这里是我们需要在mousemove事件中执行的逻辑
	...
	//设置一个定时器,300毫秒后打开锁,允许执行下次回调
	setTimeout(function(){
		canRun = true;
	}, 300);
})

从代码可以看出,我们是通过canRun变量来控制回调函数是否可以执行的。

每次触发mousemove后,我们先检查有没有上锁,如果上锁了,就什么都不做;如果没有上锁,我们就执行操作,并上锁。之后我们添加一个超时调用(setTimeout),在指定的时间后打开锁,这样再触发mousemove事件后,就又可以执行操作了。

比如在本例中,我们就可以保证回调函数中的相关操作在300毫秒内不会重复执行。下面是节流前后的效果对比:
在这里插入图片描述
节流前光标位置的输出结果(输出频率较高)
在这里插入图片描述
节流后光标位置的输出结果(输出频率明显降低)

从图中可以看到,在连续快速移动鼠标时,如果不进行节流,我们每移动几个像素就会输出一次光标位置;而节流之后,移动几十甚至上百像素才会输出一次光标位置,回调函数执行的频率明显降低了!这对网页性能来说,是一个极大的提升。

函数防抖(debounce)

我们将相关操作放在一个超时调用里,如果在该调用生效之前就发生了下一次调用,我们就取消该次调用,将新的调用添加到任务队列中等待触发。如对于输入框的输入事件,我们将代码改为如下所示:

<input id="input"></input>
<span id="error"></span>
function validateEmail( email ){
	//通过正则表达式验证邮箱格式是否正确,这里暂不实现
	...
}
//超时任务的句柄,用于清除该任务
var timer;
$("#input").on("input", function(){
	//清除上次添加但未执行的setTimeout任务
	clearTimeout(timer);
	//用户在连续输入时清除可能存在的格式错误提示
	$("#error").text("");
	//重置setTimeout任务
	timer = setTimeout(function(){
		//如果格式不正确,就给出错误提示
		if( !validateEmail( $('#input').val() ) ){
			$("#error").text("您的邮箱格式不正确!");
		} else {
			$("#error").text("");
		}
	}, 2000);
})

这样,每次输入新字符,我们就设置一个延迟,在2000毫秒(2秒)后检查邮箱格式是否正确。如果用户在2秒内输入了新字符,我们就取消上次的检查任务,并重新添加一个具有2秒延迟的检查任务。

经过上述改造,现在我们只有在输入过程中停顿至少2秒,系统才会对邮箱格式执行检查,从而避免了用户在输入过程中不断收到邮箱格式不正确的提示。

总结

函数节流和防抖都是利用了setTimeout延迟执行的特性,对函数调用过程进行一定的处理。

函数节流可以让高频调用的函数降低执行频率;而函数防抖则可以在事件连续触发时,先不执行操作,只有一定时间内没有再次触发时才去执行相关操作。两者原理相近,也都可以减少函数的执行次数,但是目的却完全不同,希望大家在使用的时候注意区分。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值