一、节流
在JavaScript中,节流(throttle)是一种常用的性能优化技术,用于限制某个函数在一定时间内的执行频率。具体来说,节流函数允许你在一段时间内只执行一次回调函数,即使在这段时间内触发了多次事件。这有助于防止因为频繁触发事件而导致的性能问题。
节流的实现原理是,在事件被触发后,一个定时器会被设置。如果在定时器完成之前,相同的事件再次被触发,那么这次触发不做任何处理,直到定时器任务完成后,清空定时器,才能对以后新的触发事件做出响应。这样,在一定的时间间隔内,只会对触发事件做一次处理。
应用场景:
- 滚动事件处理:在网页或应用中,滚动事件可能会非常频繁地触发,如果不加以控制,可能会导致性能问题。通过使用节流,可以限制滚动事件处理函数的执行频率,提高页面的响应速度和流畅度。
- 网络请求:对于需要连续发送请求的场景,如滚动加载数据或自动完成搜索,过多的请求不仅会增加服务器压力,还可能导致用户体验下降。通过节流,可以减少请求的次数,降低服务器压力,同时保证数据的及时加载。
- 动画效果:在动画过程中,如果过度频繁地刷新和渲染,可能会造成闪烁和卡顿现象。通过节流,可以控制动画的渲染频率,提高动画的流畅性和用户体验。
二、前置准备
-
准备一个html文件和一个throttle.js文件,throttle.js文件用来编写节流函数
<!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> <div class="throttle">触发节流事件</div> <script src="./throttle.js"></script> </body> </html>
// throttle.js const throttle = () => {};
-
给div绑定点击事件
<!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> <div class="throttle">触发节流事件</div> <script src="./throttle.js"></script> <script> const clickEvent = function (e) { console.log("点击事件触发", e, this) } document.querySelector(".throttle").addEventListener("click", clickEvent) </script> </body> </html>
-
将clickEvent方法传递给throttle进行处理
<!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> <div class="throttle">触发节流事件</div> <script src="./throttle.js"></script> <script> const clickEvent = function (e) { console.log("点击事件触发", e, this) console.log("执行时间", new Date().getSeconds()) } const throttleClickEvent = throttle(clickEvent, 2000) document.querySelector(".throttle").addEventListener("click", throttleClickEvent) </script> </body> </html>
// throttle.js const throttle = (fun, time) => { return fun; };
在上面修改了一下clickEvent,用来打印时间。目前的效果和一开始没什么区别,下面就开始写节流函数
三、基础节流实现
-
第一次触发事件,设置一个定时器,只要这个定时器存在,就不再对触发事件做出响应。直到定时器任务完成,清空定时器
// throttle.js const throttle = (fun, time) => { let timer; return function () { if (!timer) { timer = setTimeout(() => { fun(); timer && clearTimeout(timer); timer = null; }, time); } }; };
现在频繁触发点击事件,但会间隔2秒触发一次事件处理函数。所以基本实现了节流
-
修改this指向
// throttle.js const throttle = (fun, time) => { let timer; return function () { if (!timer) { timer = setTimeout(() => { fun.apply(this); timer && clearTimeout(timer); timer = null; }, time); } }; };
-
获取参数
// throttle.js const throttle = (fun, time) => { let timer; return function (...args) { if (!timer) { timer = setTimeout(() => { fun.apply(this, args); timer && clearTimeout(timer); timer = null; }, time); } }; };
上面就实现了一个最基本的节流函数
四、立即执行
// throttle.js
const throttle = (fun, time, immediately = false) => {
let timer;
return function (...args) {
if (!timer) {
// 如果是立即执行,则直接执行处理函数,然后设置定时器,一段时间后才可以触发
if (immediately) {
fun.apply(this, args);
timer = setTimeout(() => {
timer && clearTimeout(timer);
timer = null;
}, time);
} else {
timer = setTimeout(() => {
fun.apply(this, args);
timer && clearTimeout(timer);
timer = null;
}, time);
}
}
};
};
五、尾部执行控制
-
首先我们修改一下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> <div class="throttle">触发节流事件</div> <input type="text" class="throttle-input"> <script src="./throttle.js"></script> <script> const clickEvent = function (e) { console.log("点击事件触发", e, this) console.log("执行时间", new Date().getSeconds()) } const inputEvent = function (e) { console.log("表单数据", e, this) console.log("执行时间", new Date().getSeconds()) } const throttleClickEvent = throttle(clickEvent, 2000, true) document.querySelector(".throttle").addEventListener("click", throttleClickEvent) document.querySelector(".throttle-input").addEventListener("input", inputEvent) </script> </body> </html>
目前没有做节流处理,每次输入都会触发
-
使用节流函数控制触发频率
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>节流</title> <style> input { width: 100%; } </style> </head> <body> <div class="throttle">触发节流事件</div> <input type="text" class="throttle-input"> <script src="./throttle.js"></script> <script> const clickEvent = function (e) { console.log("点击事件触发", e, this) console.log("执行时间", new Date().getSeconds()) } const inputEvent = function (e) { console.log("表单数据", this.value) console.log("执行时间", new Date().getSeconds()) } const throttleClickEvent = throttle(clickEvent, 2000, true) const throttleInputEvent = throttle(inputEvent, 2000, true) document.querySelector(".throttle").addEventListener("click", throttleClickEvent) document.querySelector(".throttle-input").addEventListener("input", throttleInputEvent) </script> </body> </html>
此时,第一次被触发了,然后两秒后,触发了第二次。但是最后一次触发事件却没有得到响应
-
修改节流函数
// throttle.js const throttle = (fun, time, immediately = false, tail = false) => { let timer; let tailTimer; return function (...args) { if (!timer) { // 如果是立即执行,则直接执行处理函数,然后设置定时器,一段时间后才可以触发 if (immediately) { fun.apply(this, args); timer = setTimeout(() => { timer && clearTimeout(timer); timer = null; }, time); } else { timer = setTimeout(() => { fun.apply(this, args); timer && clearTimeout(timer); timer = null; }, time); } } else if (tail) { // 如果当前已经触发了响应事件,并且需要对最后一次触发做出回应,则将后面触发调用的处理函数加入定时器 // 每一次有新的触发事件,则将上一次的定时器给清除,保证始终是最新的触发事件被加入 tailTimer && clearTimeout(tailTimer); tailTimer = setTimeout(() => { fun.apply(this, args); }, time); } }; };
此时最后一次就会触发了。我们再来试一试不立即执行的情况
const throttleInputEvent = throttle(inputEvent, 2000, false)
通过控制台发现,最后一次输入的数字被打印了两次。为什么?
首先,最后打印的肯定是放在
tailTimer
里面的方法,它在53秒执行,是在51秒的时候触发的。最后一次执行节流函数是52秒,它是在50秒的时候触发的。之所以这两个方法打印了同样的值,是因为最后一次触发点击事件是在51秒,两个定时器里面的方法,都是在51秒后执行的,等它们执行的时候,都会拿到最后那个最新的值。那立即执行为什么没有这个问题呢?因为立即执行打印的是当时获取到值。简单点说:就是延迟执行,在52秒和53秒都会打印51秒最后一次触发时候的值。立即执行的话,50秒的时候会最后一次使用timer,此时,直接打印出50秒时候的值,在接下来的2秒之内,timer不会再执行,在这2秒内,触发的最后一次事件会放进
tailTimer
里面。所以,对于延迟执行的情况,其实没有必要将最后一次加入到
tailTimer
,因为timer会获取到最后一次的值。// throttle.js const throttle = (fun, time, immediately = false, tail = true) => { let timer; let tailTimer; return function (...args) { if (!timer) { // 如果是立即执行,则直接执行处理函数,然后设置定时器,一段时间后才可以触发 if (immediately) { fun.apply(this, args); timer = setTimeout(() => { timer && clearTimeout(timer); timer = null; }, time); } else { timer = setTimeout(() => { fun.apply(this, args); timer && clearTimeout(timer); timer = null; }, time); } } else if (tail && immediately) { // console.log("尾部定时器当前记录参数:", args[0].srcElement.value); // 如果当前已经触发了响应事件,并且需要对最后一次触发做出回应,则将后面触发调用的处理函数加入定时器 // 每一次有新的触发事件,则将上一次的定时器给清除,保证始终是最新的触发事件被加入 tailTimer && clearTimeout(tailTimer); tailTimer = setTimeout(() => { fun.apply(this, args); }, time); } }; };
这下打印结果就没问题了。
再来测试一下立即执行的情况
const throttleInputEvent = throttle(inputEvent, 2000, true)
立即执行的情况,就会通过
tailTimer
来打印最后的结果了。
六、完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>节流</title>
<style>
input {
width: 100%;
}
</style>
</head>
<body>
<div class="throttle">触发节流事件</div>
<input type="text" class="throttle-input">
<script src="./throttle.js"></script>
<script>
const clickEvent = function (e) {
console.log("点击事件触发", e, this)
console.log("执行时间", new Date().getSeconds())
}
const inputEvent = function (e) {
const value = this.value
console.log("表单数据", value)
console.log("执行时间", new Date().getSeconds())
}
const throttleClickEvent = throttle(clickEvent, 2000, true)
const throttleInputEvent = throttle(inputEvent, 2000, true)
document.querySelector(".throttle").addEventListener("click", throttleClickEvent)
document.querySelector(".throttle-input").addEventListener("input", throttleInputEvent)
</script>
</body>
</html>
// throttle.js
const throttle = (fun, time, immediately = false, tail = true) => {
let timer;
let tailTimer;
return function (...args) {
if (!timer) {
// 如果是立即执行,则直接执行处理函数,然后设置定时器,一段时间后才可以触发
if (immediately) {
fun.apply(this, args);
timer = setTimeout(() => {
timer && clearTimeout(timer);
timer = null;
}, time);
} else {
timer = setTimeout(() => {
fun.apply(this, args);
timer && clearTimeout(timer);
timer = null;
}, time);
}
} else if (tail && immediately) {
// console.log("尾部定时器当前记录参数:", args[0].srcElement.value);
// 如果当前已经触发了响应事件,并且需要对最后一次触发做出回应,则将后面触发调用的处理函数加入定时器
// 每一次有新的触发事件,则将上一次的定时器给清除,保证始终是最新的触发事件被加入
tailTimer && clearTimeout(tailTimer);
tailTimer = setTimeout(() => {
fun.apply(this, args);
}, time);
}
};
};
更多的优化,比如:取消、获取返回值可以参考防抖的处理。
标签:触发,throttle,节流,timer,js,面试,fun,const From: https://www.cnblogs.com/finish/p/18079015