函数节流与函数防抖

294 阅读5分钟

原理:

其实原理非常简单,巧妙地使用setTimeout来控制待执行的函数,这样可以很方便的利用clearTimeout在合适的时机来清除待执行的函数。

目的: 是为了解决函数频繁调用从而影响页面性能的问题 (简单来说就是为了节约计算机资源)。

函数节流: 指定时间间隔内只会执行一次任务(避免一直执行,虽然事件一直在触发)。

函数防抖: 任务频繁触发的情况下,只有任务触发的间隔超过指定间隔的时间才去执行相应的处理函数。

函数节流(throttle)

这里以判断页面是否滚动到底部为例,普通的做法就是监听window对象的scroll事件,然后再函数体中写入判断是否滚动到底部的逻辑:

$(window).on('scroll', function () {
	let pageHeight = $('body').height(),
		scrollTop = $(window).scrollTop(),
		winHeight = $(window).height(),
		thresold = pageHeight - scrollTop - winHeight;
	if (thresold > - 100 && thresold <= 20) {
		console.log('end');
	}
});

加上函数节流之后,当页面再滚动的时候,每隔300ms才会去执行一次判断逻辑。

简单来说,函数的节流就是通过闭包保存一个标记(canRun = true),在函数的开头判断这个标记是否为true,如果为true的话就继续执行函数,否则则 return 掉,判断完标记后立即把这个标记设为false,然后把外部传入的函数的执行包在一个setTimeout中,最后在setTimeout执行完毕后再把标记设置为true(这里很关键),表示可以执行下一次的循环了。

当setTimeout还未执行的时候,canRun这个标记始终为false,在开头的判断中被 return 掉。

function throttle(fn, interval = 300) {
	let canRun = true;
	return function () {
		if (!canRun) return;
		canRun = false;
		setTimeout(() => {
			fn.apply(this, arguments);
			canRun = true;
		}, interval);
	};
}

函数防抖(debounce)

这里以用户注册时验证用户名是否被占用为例,如今很多网站为了提高用户体验,不会再输入框失去焦点的时候再去判断用户名是否被占用,而是在输入的时候就在判断这个用户名是否已被注册:

$('input.user-name').on('input', function () {
	$.ajax({
		url: `https://just.com/check`,
		method: 'post',
		data: { username: $(this).val(), },
		success(data) {
			if (data.isRegistered) {
				$('.tips').text('该用户名已被注册!');
			} else {
				$('.tips').text('恭喜!该用户名还未被注册!');
			}
		},
		error(error) { console.log(error); },
	});
});

很明显,这样的做法不好的是当用户输入第一个字符的时候,就开始请求判断了,不仅对服务器的压力增大了,对用户体验也未必比原来的好。而理想的做法应该是这样的,当用户输入第一个字符后的一段时间内如果还有字符输入的话,那就暂时不去请求判断用户名是否被占用。在这里引入函数防抖就能很好地解决这个问题:

$('input.user-name').on('input', debounce(function () {
	$.ajax({
		url: `https://just.com/check`,
		method: 'post',
		data: { username: $(this).val(), }, success(data) {
			if (data.isRegistered) {
				$('.tips').text('该用户名已被注册!'
				);
			} else { $('.tips').text('恭喜!该用户名还未被注册!'); }
		},
		error(error) { console.log(error); },
	});
}));

其实函数防抖的原理也非常地简单,通过闭包保存一个标记来保存setTimeout返回的值,每当用户输入的时候把前一个setTimeoutclear 掉,然后又创建一个新的setTimeout,这样就能保证输入字符后的interval间隔内如果还有字符输入的话,就不会执行fn函数了。

function debounce(fn, interval = 300) {
	let timeout = null;
	return function () {
		clearTimeout(timeout);
		timeout = setTimeout(() => {
			fn.apply(this, arguments);
		}, interval);
	};
}

区别

防抖动和节流的本质是不一样的。防抖动是将多次执行变为最后一次执行,节流是将多次执行变为每隔一段时间执行。

项目应用场景:

节流的应用场景:比如,监听页面的scroll (比如判断是否滚动到了底部触发自动加载更多)、 resize事件时会频繁的调用处理函数,但是人眼能感受到的变化远远没有那么频繁。为了减少函数的调用我们就要用到函数节流。实现方式有2种:定时器或者设置一个开始时间,函数执行时计算一个时间差,时间差满足延迟时间就执行否则不执行。

防抖动的应用场景:键盘输入用户名,短信验证码时会发送ajax请求进行验证,如果不做限制,用户每次输入的时候都会去验证,提示就会生成的频繁,用户体验很差。这时就要用到函数防抖。实现方式是

(鼠标拖动功能、mousemove时计算鼠标位置,这些情况也要用到函数防抖与节流吗?目前为止我还没用到过,不太确定,以后用到了再补充。待续)

防抖 你是否在日常开发中遇到一个问题,在滚动事件中需要做个复杂计算或者实现一个按钮的防二次点击操作。 这些需求都可以通过函数防抖动来实现。尤其是第一个需求,如果在频繁的事件回调中做复杂计算,很有可能导致页面卡顿,不如将多次计算合并为一次计算,只在一个精确点做操作。因为防抖动的轮子很多,没必要自己重新造个轮子了,直接使用 underscore 的源码来解释防抖动(github.com/jashkenas/u…)。

总结一下:整体函数实现的不难。 对于按钮防点击来说的实现:一旦我开始一个定时器,只要我定时器还在,不管你怎么点击都不会执行回调函数。一旦定时器结束并设置为 null,就可以再次点击了。 对于延时执行函数来说的实现:每次调用防抖动函数都会判断本次调用和之前的时间间隔,如果小于需要的时间间隔,就会重新创建一个定时器,并且定时器的延时为设定时间减去之前的时间间隔。一旦时间到了,就会执行相应的回调函数。

生活场景

举个栗子,我们知道目前的一种说法是当 1 秒内连续播放 24 张以上的图片时,在人眼的视觉中就会形成一个连贯的动画,所以在电影的播放(以前是,现在不知道)中基本是以每秒 24 张的速度播放的,为什么不 100 张或更多是因为 24 张就可以满足人类视觉需求的时候,100 张就会显得很浪费资源。

再举个栗子,假设电梯一次只能载一人的话,10 个人要上楼的话电梯就得走 10 次,是一种浪费资源的行为;而实际生活正显然不是这样的,当电梯里有人准备上楼的时候如果外面又有人按电梯的话,电梯会再次打开直到满载位置,从电梯的角度来说,这时一种节约资源的行为(相对于一次只能载一个人)。

Ref:

mp.weixin.qq.com/s/I3Ru3aOtP… www.cnblogs.com/lijiayi/p/j… blog.coding.net/blog/the-di…