1. 防抖(Debounce)
防抖是指在某个事件频繁触发时,延迟一定时间后再执行回调函数。如果在延迟时间内又触发了同样的事件,那么会重新计时。从而避免了频繁触发回调函数,只在事件停止触发后执行一次。
就拿王者荣耀游戏来说,防抖就好比游戏里面的回城,如果在回城时间内再次点击回城(回城被打断),则会重新计算回城时间。
防抖实现步骤图如下:
应用场景:
- 输入框搜索建议:当用户输入时,防抖可以用于延迟发送请求,避免频繁请求后端接口。
- 窗口大小调整:当窗口大小调整时,防抖可以用于调整事件触发的频率,避免频繁操作导致页面抖动。
- 按钮防重复点击:当用户频繁点击按钮时,防抖可以用于限制按钮点击的频率,避免重复提交操作。
下面我拿上面第三点防抖的应用场景,来和大家一起一步一步手写防抖效果的代码:
首先我们先在body里面创建一个提交按钮,并且给它绑定点击监听事件,假设我们点击按钮后要执行send
函数,让其打印我们好棒鸭!,那我们的初步代码就是下面这样啦。
<body>
<button id="btn">提交</button> //创建一个提交按钮
</body>
</html>
<script>
function send(){
console.log('我们好棒鸭!');
}
const btn = document.getElementById("btn");
btn.addEventListener("click",send) //给btn按钮绑定点击监听事件
</script>
好,那我们点击按钮15次之后,就得到了下面的结果:打印了15次
那我们一直点它就一直打印,要是是这样发送请求的话,服务器不得被干冒烟,所以这时候我们就要用防抖来限制按钮点击的频率,防止用户手抖重复提交操作。接下来,我们继续理思路,限制按钮点击频率,在这里就是限制用户一定时间不点击了才响应,所以我们肯定需要给事件函数传一个限定时间参数,所以我们在监听事件里单纯写send函数名是肯定行不通了,因为这样传递不了参数,加了参数就相当于只在加载页面调用一次。所以这时候我们就要用到闭包的知识了,我们在按钮监听事件里面写个debounce函数的调用,并且将send函数名作为debounce函数实参,通过页面加载将debounce调用,然后将它内部里面的函数返回出来,从而实现每次点击按钮依旧可以实现send的调用。这时候我们还可以再给debounce(send,1000)函数加个1秒的延迟时间作为实参,只有在这段时间用户不再点击按钮了,我们才执行需要的事件,这里就是执行send()方法,这时我们再写一下代码。
<script>
function send(){
console.log('我们好棒鸭!');
}
const btn = document.getElementById("btn");
btn.addEventListener("click",debounce(send,1000))
function debounce(send,time){
return function(){
setTimeout(function(){
send()
}, time);
}
}
</script>
那我们的代码就写成这样啦,当页面运行时,会立即执行debounce(send,1000),然后会返回一个函数给监听事件,每次按钮被点击便会执行这个debounce函数返回的函数。这时候我们运行代码点击按钮后,便会在1秒后打印结果,不过多次点击也还是会创建多个定时器最后打印多个结果。如下图:
所以,这时候在debounce函数声明一个name变量,用来记录定时器的名字,然后在返回函数中,我们将每次点击创建的定时器赋值给name,所以我们只要在这之前判断name是否有值,如果name有值,说明上一次点击事件创建的定时器还没有执行完,所以这时我们只需把上次的定时器清空,则不会再执行上次的点击事件了,直到在延时时间内不再点击,则才会执行我们需要执行的事件。这时代码如下:
<script>
function send(){
console.log('我们好棒鸭!');
}
const btn = document.getElementById("btn");
btn.addEventListener("click",debounce(send,1000))
function debounce(send,time){
let name;
return function(){
if(name) clearTimeout(name) //name有值,说明上次定时器还没执行完,这时清除上次定时器
name = setTimeout(function(){
send()
}, time);
}
}
</script>
这时候我们运行后会发现,不管我们点击多少次按钮,在我们不点击后1秒,最后只会打印一个结果了。
好!这时候低配版的的防抖我们就已经实现啦~哈哈哈,这还是个低配的?为什么这么说呢,我们再来看看this的指向问题,按理说按钮执行了监听事件this应该要指向btn的,这时我们在send函数中打印this看看: 结果是指向window。很显然我们现在这样写影响了send函数中this的指向问题,所以我们还得用call方法,将this指向btn,同时也还有一个问题,就是如果send函数要接收多个参数,那返回函数里调用send()也得传多个参数,是不是就太麻烦啦,所以这时候还要用到arguments类数组,它包含传递给函数的每个参数,将arguments里的每个参数展开到send函数中作为实参即可。这时候代码如下:
<script>
function send(){
console.log('我们好棒鸭!',this);
}
const btn = document.getElementById("btn");
btn.addEventListener("click",debounce(send,1000))
function debounce(send,time){
let name;
return function(){
let _this = this;//这个this指向btn
let arr = arguments //包含传递给函数的每个参数
if(name) clearTimeout(name) //name有值,说明上次定时器还没执行完,这时清除上次定时器
name = setTimeout(function(){
send.call(_this,...arr) //将send函数的this指向_this(也就是btn)
//arguments里的每个参数展开到send函数中作为实参
}, time);
}
//也可以使用箭头函数这样写
// function debounce(send,time){
// let name;
// return function(){
// let arr = arguments //包含传递给函数的每个参数
// if(name) clearTimeout(name) //name有值,说明上次定时器还没执行完,这时清除上次定时器
// name = setTimeout(()=>{ //这里使用箭头函数,因为箭头函数没有this,所以里面的this依旧指向btn
// send.call(this,...arr) //将send函数的this指向btn(此时this指向btn)
// //arguments里的每个参数展开到send函数中作为实参
// }, time);
// }
}
</script>
最后运行结果如下:
到这里我们的防抖代码就成功实现啦!这里面每一条代码都运用的非常巧妙,需要我们对闭包,this指向,以及第二种方法箭头函数的知识都要了解的非常清楚,知道了这些,是不是觉得手写防抖代码很简单啦~
2. 节流(Throttle)
节流是指在某个事件持续触发时,限制一定时间间隔只执行一次回调函数。比如,设置每1000毫秒最多执行一次回调函数,不论事件触发的频率有多高。
同样拿王者荣耀游戏来说,节流就好比游戏里面英雄的普通攻击,在限制一定时间点击多次,也只能攻击一次。
节流实现步骤图如下:
应用场景:
- 页面滚动加载:当用户滚动页面时,节流可以用于限制加载事件的触发频率,避免过多的加载请求。
- 频繁点击按钮:当用户频繁点击按钮时,节流可以用于限制按钮点击的频率,避免过于频繁的操作。
同样,我们通过上面的应用场景2,来和大家一起写节流的示例代码:
这里和防抖代码一样,初始代码如下:
<body>
<button id="btn">提交</button>
</body>
<script>
function send(){
console.log('我们好棒鸭!');
}
const btn = document.getElementById("btn");
btn.addEventListener("click",throttle(send,1000))
function throttle(send,delay){
return function(){
send()
}
}
</script>
这里我们设置每1000毫秒最多执行一次回调函数,不论事件触发的频率有多高。所以这时候我们需要在每次调用send()函数前判断上次点击的时间和下次点击的时间差是否大于延时时间,如果大于则执行send函数,否则就一直等待到下次点击时间与上次点击时间差大于延时时间。在这里,这样就保证了每次在1000毫秒里,最多只能触发一次send函数。下面我们看看代码:
<script>
function send(){
console.log('我们好棒鸭!');
}
const btn = document.getElementById("btn");
btn.addEventListener("click",throttle(send,1000))
function throttle(send,delay){
//pretime记录的是上次点击的时间(初始值为页面运行时间)
let pretime = Date.now()
return function(){
//前判断上次点击的时间和下次点击的时间差是否大于延时时间
if(Date.now() - pretime > delay){
send()
pretime = Date.now()
}
}
}
</script>
这里面pretime记录的是上次点击的时间(初始值为页面运行时间),然后Date.now是我们点击的时间,只有它们相差1s才会执行send函数,同时更新prevtime为当前点击时间(相当于下次点击前的上次点击时间),然后下次再次点击又会有一个点击时刻的时间,若这个时间和prevtime差不超过1s,则不执行结果,一直到下次点击时间与prevtime差大于1s,才执行send函数。所以这样就非常优雅的保证了每次1s内无论我们点击多少次都只会执行一次send函数。
运行结果如下:
同样,和防抖一样,我们还要解决send函数this的指向问题,可以用call方法让它指向btn,还要用到arguments类数组,它包含传递给函数的每个参数,将arguments里的每个参数展开到send函数中作为实参。这样我们的节流代码也就成功实现啦~看看最终代码:
<script>
function send(){
console.log('我们好棒鸭!',this);
}
const btn = document.getElementById("btn");
btn.addEventListener("click",throttle(send,1000))
function throttle(send,delay){
let pretime = Date.now()
return function(){
let arr = arguments //包含传递给函数的每个参数
if(Date.now() - pretime > delay){//
send.call(this,...arr)//此时this就是指向btn
pretime = Date.now()
}
}
}
</script>
最后运行结果如下:
喔哦,非常棒呢!大家是不是觉得节流代码也非常简单呢~节流代码也是每一条代码都非常有用呢,要是都可以搞明白,说明大家对闭包和this指向问题都非常了解啦~
总结
防抖:在规定时间内,多次触发只响应最后一次。(多次触发,只执行最后一次)
节流:在规定时间内,多次触发只响应第一次。(规定时间内,只触发一次)
以上代码分别展示了防抖和节流的实现方式。我们可以根据实际需要选择防抖还是节流来优化事件触发的频率,提升用户体验。
如果觉得文章对您有所帮助的话,麻烦给小博主点点关注,点点赞咯😘,有问题欢迎各位小伙伴评论喔😊~