函数的防抖与节流

函数防抖和节流,都是控制事件触发频率的方法。应用场景有很多,输入框持续输入,将输入内容远程校验、多次触发点击事件、onScroll等等。


防抖

函数防抖,这里的抖动就是执行的意思,而一般的抖动都是持续的,多次的。假设函数持续多次执行,我们希望让它冷静下来再执行。也就是当持续触发事件的时候,函数是完全不执行的,等最后一次触发结束的一段时间之后,再去执行。

分解一下需求:

持续触发不执行

不触发的一段时间(最后一次点击)之后再执行

那么怎么实现上述的目标呢?我们先看这一点:在不触发的一段时间之后再执行,那就需要个定时器呀,定时器里面调用我们要执行的函数,将arguments传入。

封装一个函数,让持续触发的事件监听是我们封装的这个函数,将目标函数作为回调(func)传进去,等待一段时间过后执行目标函数

1
2
3
4
5
6
7
8

function debounce(func, delay) {
return function() {
setTimeout(() => {
func.apply(this, arguments)
}, delay)
}
}

第二点实现了,再看第一点:持续触发不执行。我们先思考一下,是什么让我们的函数执行了呢?是上边的setTimeout。OK,那现在的问题就变成了持续触发,不能有setTimeout。这样直接在事件持续触发的时候,清掉定时器就好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function debounce(func, delay) {
let timeout
return function() {
clearTimeout(timeout) // 如果持续触发,那么就清除定时器,定时器的回调就不会执行。
timeout = setTimeout(() => {
func.apply(this, arguments)
}, delay)
}
}
用法:

box.onmousemove = debounce(function (e) {
box.innerHTML = `${e.clientX}, ${e.clientY}`
}, 1000)

节流

节流的意思是让函数有节制地执行,而不是毫无节制的触发一次就执行一次。什么叫有节制呢?就是在一段时间内,只执行一次。

同样,我们分解一下:

持续触发并不会执行多次

(第一次执行之后)到一定时间再去执行

思考一下,持续触发,并不会执行,但是到时间了就会执行。抓取一个关键的点:就是执行的时机。要做到控制执行的时机,我们可以通过一个开关,与定时器setTimeout结合完成。

函数执行的前提条件是开关打开,持续触发时,持续关闭开关,等到setTimeout到时间了,再把开关打开,函数就会执行了。

我们看一下代码怎么实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
function throttle(func, delay) {
let run = true
return function () {
if (!run) {
return // 如果开关关闭了,那就直接不执行下边的代码
}
run = false // 持续触发的话,run一直是false,就会停在上边的判断那里
setTimeout(() => {
func.apply(this, arguments)
run = true // 定时器到时间之后,会把开关打开,我们的函数就会被执行
}, delay)
}
}

调用的时候:

1
2
3
4
box.onmousemove = throttle(function (e) {
box.innerHTML = `${e.clientX}, ${e.clientY}`
}, 1000)

这样,就实现了节流,节流还可以用时间间隔去控制,就是记录上一次函数的执行时间,与当前时间作比较,如果当前时间与上次执行时间的时间差大于一个值,就执行。

总结

防抖和节流巧妙地用了setTimeout,来控制函数执行的时机,优点很明显,可以节约性能,不至于多次触发复杂的业务逻辑而造成页面卡顿。

介绍节流防抖原理、区别以及应用

防抖:多次触发事件,事件处理函数只能执行一次,并且是在触发操作结束时执行。也就是说,当一个事件被触发准备执行事件函数前,会等待一定的时间(这时间是码农自己去定义的,比如 1 秒),如果没有再次被触发,那么就执行,如果被触发了,那就本次作废,重新从新触发的时间开始计算,并再次等待 1 秒,直到能最终执行

节流:事件触发后,规定时间内,事件处理函数不能再次被调用。也就是说在规定的时间内,函数只能被调用一次,且是最先被触发调用的那次

使用场景

防抖:搜索框搜索输入,并在输入完以后自动搜索、手机号,邮箱验证输入检测、窗口大小 resize 变化后,再重新渲染。
节流:滚动加载更多、搜索框搜的索联想功能、高频点击、表单重复提交……

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

/**
* 防抖函数 一个需要频繁触发的函数,在规定时间内,只让最后一次生效,前面的不生效
* @param fn要被节流的函数
* @param delay规定的时间
*/
function debounce(fn, delay) {
//记录上一次的延时器
var timer = null;
return function () {
//清除上一次的演示器
clearTimeout(timer);
//重新设置新的延时器
timer = setTimeout(function(){
//修正this指向问题
fn.apply(this);
}, delay);
}
}
document.getElementById('btn').onclick = debounce(function () {
console.log('按钮被点击了' + Date.now());
}, 1000);
* 节流函数 一个函数执行一次后,只有大于设定的执行周期才会执行第二次。有个需要频繁触发的函数,出于优化性能的角度,在规定时间内,只让函数触发的第一次生效,后面的不生效。


* @param fn要被节流的函数
* @param delay规定的时间
*/
function throttle(fn, delay) {
//记录上一次函数触发的时间
var lastTime = 0;
return function(){
//记录当前函数触发的时间
var nowTime = Date.now();
if(nowTime - lastTime > delay){
//修正this指向问题
fn.call(this);
//同步执行结束时间
lastTime = nowTime;
}
}
}

document.onscroll = throttle(function () {
console.log('scllor事件被触发了' + Date.now());
}, 200);


参考文章:函数的防抖和节流是个啥???
介绍节流防抖原理、区别以及应用