我们在写项目中总会遇到一些情况,就是在设置了一些监听事件后,你会发现这个事件触发太容易、太频繁了。比如我们监听浏览器滚动,并且每次触发了浏览器滚动事件就输出下提示。
<!DOCTYPE html>
<html lang="en">
<body>
<div style="height: 8600px;"></div>
<script>
window.onscroll = function () {
console.log('你滚动了');
}
</script>
</body>
</html>
但你会发现,只要轻轻一滚动鼠标滚轮,就会输出十几条信息,触发频率太高了。
在上面例子监听事件只是简单的输入,还好浏览器可以完全胜任这个工作。但如果我们监听事件是非常复杂的请求,或者有些人非常喜欢点击按钮(疯狂点几十下那种),而这个按钮是请求事件的按钮,每点击一次按钮就会发送一次请求,浏览器一下子进行这么多的请求,多累啊,总会把浏览器累死的(卡死)。
所以你就要防止这个人手抖(防抖),不要他点这么多下鼠标。
1.防抖
防抖:监听的元素被触发的时候,不会立刻执行监听函数,而是等待一段时间(自己定义的时间),如果等待的这段时间内元素没有再被触发过了,就执行监听函数。如果等待的这段时间内元素又被触发了,又重新等待一段时间,就这样重复重复,直到元素没有再被触发了,再执行函数。说白了就像是电梯关门,一直有人在就不会关门,没有人了过一会就会自动关门。
例子:对按钮的点击事件进行防抖。
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<button id="btn">点击一下</button>
<script>
//获取到按钮的dom
var btn = document.getElementById('btn')
function showTip() {
console.log('你点击了我');
}
//防抖的实现用到了闭包可以访问到外部函数的变量,而且这个变量不会被垃圾回收机制处理
//所以timer这个变量可以存储上次事件触发的定时器。
function debounce(fun, delay) {
let timer;//这个变量用来表示定时器
return function () {
//如果事件又被触发了,我们清空timer,timer表示上一次的定时器
//这个上一次的定时器timer我们利用闭包存储的
clearTimeout(timer);
//注意:setTimeout()会返回一个唯一的值,可以表示当前这个定时器
//我们用timer存储setTimeout()的返回值
//然后我们重置定时器
timer = setTimeout(() => {
fun()
}, delay)
}
}
//给按钮绑定监听函数,这个函数的参数需要一个回调函数,和一个等待时间。
btn.onclick = debounce(showTip, 2000)
</script>
</body>
</html>
2.节流
节流:功能和防抖有点相似,也是在触发一段时间后才执行函数,但是有一点不一样。防抖是等元素没有再被触发了才执行函数,感觉这样白白等下去有点傻呀,而节流就是在某个时间间隔之后就会执行监听函数。
说白了,有个人疯狂点击鼠标触发事件的时候,如果你用防抖,要等他疯狂点击后才会执行函数,这个人都在疯狂点击鼠标了,肯定很着急鸭,他可能等不到你执行监听函数就关闭你网站了,然后心里想着‘垃圾网站’。所以用节流就可以在他疯狂点击鼠标期间的一段时间内,执行监听函数,既考虑了浏览器,又考虑了用户。
例子还是按钮点击事件:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<button id="btn">点击一下</button>
<script>
var btn = document.getElementById('btn')
function showTip() {
console.log('你点击了我');
}
function throttling(fun, delay) {
//还是利用闭包的特性来储存上次的定时器timer
let timer;
return function () {
let context = this;//这是为了防止fun中this改变了
let args = arguments;//防止fun中的参数因为闭包无了
//如果上次的定时器还存在就直接返回了,因为是间隔时间内才输出一次
//如果定时器存在,表示这段时间间隔内已经输出过了
if (timer) {
return;
}
//如果上次定时器结束了,就重新设置定时器
timer = setTimeout(() => {
fun.apply(context, args)//利用apply改变fun的this指向
timer = null;//上一次的定时器就置空就行
}, delay)
}
}
btn.onclick = throttling(showTip, 1000)
</script>
</body>
</html>
另一种写法,利用Date函数,通过上次触发事件的时间和这次触发事件的时间差,进行判断,然后节流。
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<button id="btn">点击一下</button>
<script>
var btn = document.getElementById('btn')
function showTip() {
console.log('你点击了我');
}
function throttling(fun, delay) {
let old = 0;
return function () {
let context = this;
let args = arguments;
//now变量表示当前点击时的时间戳,这个值很大
let now = new Date();
if (now - old > delay) {
fun.apply(context, args)
old = now;//改变old的值,并且old值利用闭包特性储存着
}
}
}
btn.onclick = throttling(showTip, 1000)
</script>
</body>
</html>