前言
防抖和节流都是为解决短时间内频繁触发某个功能函数而导致的性能问题。比如,触发频率过高而导致响应速度跟不上,以致出现延迟,假死或卡顿的现象。
且在实际的开发中,大多都能遇上,所以掌握这个知识点是很有必要的。但,二者应对的业务需求并不一样,因此实现的原理也是不一样的。下面就让我们来一起看看吧。
防抖(debounce)
介绍
当事件被触发n秒后,执行事件处理函数。若是在这n秒内,该事件又被触发,则重新计时,而不会执行事件处理函数。
它的原理是维护一个计时器,规定在delay时间之后触发回调,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。效果上看,就是将几次操作合并为一此操作进行,也就是只有最后一次操作会被触发。
应用场景
-
搜索,用户输完字符之后,再发送请求
-
使用防抖,让用户在不断地缩放浏览器的窗口或滚动文档时,只会触发一次相对应的resize或scroll事件。
const debounce = function (func, delay) {
let timerId = null;
return function() {
timerId && clearTimeout(timerId);
timerId = setTimeout(function() {
func.apply(this, arguments);
}, delay);
}
}
function handle() {
console.log('再次滚动了');
}
window.addEventListener('scroll', debounce(handle, 1000));
复制代码
将上面的防抖代码付诸实践后,我们可以看到,当持续地触发scroll事件时,handle(事件处理函数)仅在停止滚动1000毫秒后才会调用一次,也就是说在持续触发scroll事件的过程中,handle并没有执行。
节流(throttle)
介绍
当持续性触发事件时,确保一定时间段内只会调用一次事件处理函数。其实现原理:是通过判断是否到达一定时间来触发回调函数。
应用场景
-
一定时间内多次点击一个功能按钮。
-
页面无限加载。需用户滚动页面时,每隔一段时间发一次 ajax 请求,而不是只要滚动就请求数据。
-
通过resize,使浏览器页面窗口放大缩小。
案例
函数节流实现方式:时间戳、定时器、时间戳+定时器。
时间戳版
const throttle = function(func, delay) {
let prev = Date.now();
return function() {
let now = Date.now();
if (now - prev >= delay) {
func.apply(this, arguments);
prev = Date.now();
} else {
console.log('调用时间不满足');
}
}
}
function handle() {
console.log('再次滚动了');
}
window.addEventListener('scroll', throttle(handle, 1000));
复制代码
时间戳方式:有两点需要注意。
-
假如页面还未加载完成,就高频率触发事件(这里指滚动),第一次会立即执行。 因为,这时的加载时间已大于delay。
-
若最后一次触发回调和前一次触发的差小于delay,则最后一次不会触发。
定时器版
const throttle = function(func, delay) {
let timer = null;
return function() {
if (!timer) {
timer = setTimeout(function() {
func.apply(this, arguments);
timer = null;
}, delay);
} else {
console.log('调用时间不满足');
}
}
}
function handle() {
console.log('再次滚动了');
}
window.addEventListener('scroll', throttle(handle, 1000));
复制代码
当第一次触发事件时,不会立即调用事件处理函数,而是在delay毫秒后才调用。之后无论怎么频繁地触发事件,也都是每delay毫秒才调用一次。当最后一次触发停止后,由于定时器会延迟delay毫秒,所以还会调用一次处理函数。
时间戳+定时器
看了上面时间戳或定时器实现的节流,我们了解到,单一使用它们的任何一个,都存在一定的缺陷。但是,当我们将两者结合使用时,你会发现,它能在第一次触发事件时立即执行事件处理函数,且在最后一次触发事件后也能够执行一次事件处理函数。
const throttle = function(func, delay) {
let timerId = null;
let startTime = Date.now();
// 开始时间:startTime 当前时间:currentTime 延迟时间:delay 剩余时间:remainTime
return function() {
let currentTime = Date.now();
let remainTime = delay - (currentTime - startTime);
clearTimeout(timerId);
if (remainTime <= 0) {
func.apply(this, arguments);
startTime = Date.now();
} else {
timerId = setTimeout(func, remainTime);
}
}
}
function handle() {
console.log('再次滚动了');
}
window.addEventListener('scroll', throttle(handle, 1000));
复制代码
当 remainTime <= 0 时,则执行事件处理函数,它确保第一次触发事件就能立即执行事件处理函数和每隔delay时间执行一次事件处理函数。
当 remainTime > 0 时,则设定延迟remainTime时间后,执行事件处理函数。这样便保证了最后一次触发事件后,仍然能执行一次事件处理函数。若是在remainTime这段时间内,再次触发事件,则取消当前的计时器,并重新计时。
结束语
防抖和节流能有效减少浏览器引擎的损耗,防止出现页面堵塞卡顿现象,建议同学们熟练掌握。