0%

函数节流和函数防抖

函数节流和函数防抖都是在时间轴上控制函数的执行次数。

一、函数节流(throttle)

概念: 规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
React.useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);

const handleScroll = () => {
// 判断是否滚动到底部的逻辑
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,然后handleScroll函数的执行包在 setTimeout 中,最后在 setTimeout 执行完毕后再把标记设置为 true,表示可以执行下一次的循环了。当 setTimeout 还未执行的时候,canRun 这个标记始终为 false,在函数开始的判断中被 return 掉。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
React.useEffect(() => {
window.addEventListener("scroll", handleFun);
return () => window.removeEventListener("scroll", handleFun);
}, []);

let canRun = true;

const handleFun = () => {
return () => {
if (!canRun) {
return;
}
canRun = false;
setTimeout(() => {
handleScroll();
canRun = true;
}, 300);
}
}

const handleScroll = () => {
// 判断是否滚动到底部的逻辑
let pageHeight = body.height(),
scrollTop = window.scrollTop(),
winHeight = window.height(),
thresold = pageHeight - scrollTop - winHeight;
if (thresold > -100 && thresold <= 20) {
console.log('end');
}
};

二、函数防抖(debounce)

概念: 在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。

生活中的实例: 如果有人进电梯(触发事件),那电梯将在10秒钟后出发(执行事件监听器),这时如果又有人进电梯了(在10秒内再次触发该事件),我们又得等10秒再出发(重新计时)。

这里以用户注册时验证用户名是否被占用为例,如今很多网站为了提高用户体验,不会在输入框失去焦点的时候再去判断用户名是否被占用,而是在输入的时候就在判断这个用户名是否已被注册。这样的做法不好的是当用户输入第一个字符的时候,就开始请求判断了,不仅对服务器的压力增大了,对用户体验也未必比原来的好。

而理想的做法应该是这样的,当用户输入第一个字符后的一段时间内如果还有字符输入的话,那就暂时不去请求判断用户名是否被占用。

在这里引入函数防抖就能很好地解决这个问题,其实函数防抖的原理也非常地简单,通过闭包保存一个标记来保存 setTimeout 返回的值,每当用户输入的时候把前一个 setTimeout clear 掉,然后又创建一个新的 setTimeout,这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不去请求判断用户名是否被占用了。

1
2
3
4
5
6
7
8
9
10
11
12
13
let timeout = null;

const checkNameFun = () {
return () => {
if(timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(() => {
//TO DO 调接口检查是否用户名是否存在
doCheckUserName(name)
}, 300);
};
}