什么是防抖(节流)
在实际的网页交互中,如果一个事件高频率的触发,这会占用很多内存资源,但是实际上又并不需要监听触发如此多次这个事件(比如说,在抢有限数量的优惠券时,用户往往会提前在短时间内高频率的点击按钮,但是我只需要接受多次点击中的一次点击,判断有没有抢到即可),这个时候就需要防抖来减少监听的次数,
防抖就是在多次触发事件时,减少对冗余事件的监听(主要包括,只保留一次监听和一定事件内只触发一次监听),
防抖使用场景 :在原有需求能实现的前提下减少多余操作
原理和示例
事件案例
以下是一个点击事件的展示,我们希望判断这个按钮有没有被点击(只要又打印结果即可)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>防抖</title>
</head>
<body>
<button id="bt">点击</button>
<script src="index.js"></script>
</body>
</html>
const bt = document.getElementById('bt');
const click = () => {
console.log('点击了按钮');
}
bt.addEventListener('click', click);
可以看到短时间内用户可能会多次点击按钮,但是我们只需要一次打印就可以判断出用户是否点击了按钮,所以这里有16次的事件冗余。
设置防抖
思路:在用户多次点击按钮时,我们选择保留最后一次事件触发,即在点击到事件触发这段时间,如果用户再点击,则将上一次点击的操作取消,而等待执行新点击的操作,以此类推;
const bt = document.getElementById('bt');
let timer;// 声明一个全局的定时器变量
const click = () => {
clearTimeout(timer);// 清除之前的定时器
timer = setTimeout(() => { // 延时触发操作
console.log('点击了按钮');
}, 500);
}
bt.addEventListener('click', click);
这里的代码要注意这个定时器变量,它要在click函数体外,因为它要在click触发时保存上一次的定时器,并对其实现清楚,如果出现在click函数体内,则会因为每次都产生新的定时器且无法捕获到上一次的定时器,而导致所有打印延迟生效,没有被清楚
此时在点击按钮时,不会立刻打印结果,而多次点击时,由于旧的定时器不断被清楚,打印操作都不会被触发,直到停下点击之后的0.5s才会出现打印结果
封装一个防抖函数
了解了防抖的基本实现,我们封装一个防抖函数以便于对函数的时间间隔防抖功能实现,
防抖函数,它应该接受两个参数,一个是要执行的函数,一个是执行函数的间隔,并且返回一个有防抖效果的函数
/**
* @param fun 要执行的操作
* @param time 执行的间隔
* @return 返回有防抖效果的原操作
* */
const FD = (fun, time) => {
let timer;// 闭包储存定时器变量
return function () {
if (!timer) {
timer = setInterval(() => {
fun();
clearInterval(timer);// 清除定时器
timer = null;// 重置定时器变量
}, time);
}
}
}
因为要有一个变量来保存定时器的状态,所以这采用闭包的形式保存这个timer,这样每次执行这个FD的返回函数时,timer的值都会保留下来而不是被覆盖,
关于闭包的解释可以参考:js闭包------简单理解闭包含义_js 闭包累加-CSDN博客
使用实例
监听一个页面滚动的事件,打印出滚动的高度,用防抖减少打印的次数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>防抖</title>
</head>
<body style="height: 200vh;">
<button id="bt">点击</button>
<script src="index.js"></script>
</body>
</html>
window.onscroll = () => {
console.log('滚动的距离', window.scrollY);
}
这里即使是滚动一小段距离也会多次触发打印
滚动了300的距离就触发多次打印,
/**
* @param fun 要执行的操作
* @param time 执行的间隔
* @return 返回有防抖效果的原操作
* */
const FD = (fun, time) => {
let timer;// 闭包储存定时器变量
return function () {
if (!timer) {
timer = setInterval(() => {
fun();
clearInterval(timer);// 清除定时器
timer = null;// 重置定时器变量
}, time);
}
}
}
window.onscroll = FD(() => {
console.log('滚动的距离', window.scrollY);
},500);
有了防抖之后,0.5秒只触发一次打印,大大减少了事件触发
完整代码展示
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>防抖</title>
</head>
<body style="height: 200vh;">
<button id="bt">点击</button>
<script src="index.js"></script>
</body>
</html>
index.js:
// 1.声明一个变量,储存定时器,
// 2.在点击事件中,先清除之前的定时器,再设置新的定时器,
// 3.设置定时器延时操作,相当于等待操作执行结束,才会执行下一次操作(防抖)
const bt = document.getElementById('bt');
let timer;// 声明一个全局的定时器变量
const click = () => {
clearTimeout(timer);// 清除之前的定时器
timer = setTimeout(() => { // 延时触发操作
console.log('点击了按钮');
}, 500);
}
bt.addEventListener('click', click);
// 多次点击,只执行最后一次操作(节流)
// 节流
/**
* @param fun 要执行的操作
* @param time 执行的间隔
* @return 返回有防抖效果的原操作
* */
const FD = (fun, time) => {
let timer;// 闭包储存定时器变量
return function () {
if (!timer) {
timer = setInterval(() => {
fun();
clearInterval(timer);// 清除定时器
timer = null;// 重置定时器变量
}, time);
}
}
}
// window.onscroll = () => {
// console.log('滚动的距离', window.scrollY);
// }
window.onscroll = FD(() => {
console.log('滚动的距离', window.scrollY);
},500);
// 滚动时,每隔500ms打印一次滚动的距离