防抖和节流
防抖(debounce)
在一定时间内连续触发同一事件,事件处理函数只执行一次,且是在最后一次触发的时候执行
现在有一个input标签:
<input type="text" >
监听输入事件,并打印出来输入的内容
let inp = document.querySelector("input");
inp.oninput = function() {
console.log(this.value)
}
会发现这个事件执行了很多次:
那么如果每一次输入都是跟后台进行一次数据交互,就会非常影响性能
怎么样才能让请求次数没那么多?
// 用户一输入,就会开启一个定时器,只要还在继续输入,就清除掉这个定时器,直到最后一次输入
let inp = document.querySelector("input");
let timer = null; // 控制事件是否触发
inp.oninput = function() {
// 进入此分支,说明当前正在一个计时过程中(0.5s内),而且又触发了此事件,所以要清除掉这个计时,重新开始计时
if(timer !== null) {
clearTimeout(timer);
}
// 进入了此分支表示当前并没有在计时,那就开启一个计时
timer = setTimeout(() => {
console.log(this.value);
},500)
}
但是我们上面的代码不太好,首先是建了一个全局变量timer
,而且我们的防抖代码和业务逻辑代码console.log(this.value);
混在了一起
那么怎么解决呢?
我们可以利用闭包来封装一个防抖的函数
function debounce() {
}
希望实现的效果是:
用防抖来约束oninput
事件,oninput
事件执行的函数是这里传进来的参数function(){}
,我们的业务逻辑也写在里面,防抖的延迟是500ms
let inp = document.querySelector("input");
let timer = null; // 控制事件是否触发
inp.oninput = debounce(function(){}, 500);
我们知道,oninput
应该等于一个函数,
inp.oninput = function(){}
那么现在其实是将调用debounce
的结果赋值给了oninput
inp.oninput = debounce(function(){}, 500) {}
所以说debounce
的返回值肯定是一个函数,它返回的值就是oninput
这个事件执行的函数,而且我们还需要一个参数fn
,fn
要传一个函数,里面写业务逻辑代码,另一个参数delay
就是防抖的延迟时间
timer
是外部函数的变量,return
的是内部函数,内部函数使用了外部函数的变量,所以timer
不会被销毁
function debounce(fn, delay) {
let timer = null;
return function() {
if(timer !== null) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn();
}, delay)
}
}
我们这个时候调用debounce()
来试试可不可以
inp.oninput = debounce(function() {
console.log(this.value);
},500)
会发现没有正常输出,输出的是undefined
我们将代码改成console.log(this)
:
此时这个this
指向的不是inp
,而是全局对象,this
是由传进去的这个函数执行,传进去之后执行的其实是fn()
,调用这个fn()
的其实是window
对象
而我们return
的那个内部函数function(){}
才是由inp
执行,所以我们要由inp
调用fn()
才行,用call
改变调用fn
的对象,改成用this
调用,而this
就是这个inp
事件对象
timer = setTimeout(() => {
// fn();
fn.call(this);
},delay)
节流(throttle)
在一定时间内连续触发同一事件,函数立即执行一次,之后该函数在指定时间期限内不再工作,直至过了这段时间才能重新生效
给页面加个滚动条:
<style>
body {
height: 200px;
}
</style>
监听一个滚动事件
window.onscroll = function() {
console.log(123);
}
我们随便拉一下滚动条,这个事件会被执行非常多次,那如果我们这里的业务逻辑再复杂一些,或者要跟后台进行数据交互,这也会很影响性能
我们对比一下防抖和节流:
防抖是只执行最后一次,节流是控制执行次数
// 执行完setTimeout将状态->true,没执行就将状态->false,也就是每0.5s才会再执行一次,就将执行的频率降低了
let flag = true; // 状态位
window.onscroll = function() {
if(flag) {
setTimeout(() => {
console.log(123);
flag = true;
},500)
}
flag = false; // 休息时间
}
这个时候再随意滚动,还是会执行事件,但是次数大大减少
封装:(和上面的防抖一样的逻辑)
function throttle(fn, delay) {
let flag = true; // 状态位
return function() {
if(flag) {
setTimeout(() => {
fn.call(this);
flag = true;
},delay)
}
flag = false; // 休息时间
}
}
调用:
window.onscroll = throttle(function(){
console.log(123);
},500)
⚠️:节流并不只这一种实现方案,还可以完全不借助setTimeout
,将状态位换成时间戳,利用时间戳差值是否大于指定的间隔时间来做判断
完