提示:
本文为JavaScript栏目:JavaScript高级学习:事件的防抖和节流02——详解防抖
JavaScript高级学习:事件的防抖和节流02——详解防抖
防抖
防抖的思路
如果在delay时间内没有再次触发滚动事件,那么就执行函数
如果在delay时间内再次触发滚动事件,那么当前的计时取消,重新开始计时
简单来说就是把事件的触发当做成一个技能。在技能释放的时候需要delay时间的蓄力,而且不能被打断,如果被打断则会重新开始蓄力。
初步实现
既然前面都提到了计时,那实现的关键就在于setTimeout这个函数,由于还需要一个变量来保存计时,考虑维护全局纯净,可以借助闭包来实现
<script>
let app=document.getElementById("app");
function fun(ev) {
app.innerHTML = parseInt(app.innerHTML)+1;
}
function debounce(fun, delay) {
let timeOut;
return function() {
console.log(timeOut)
if (timeOut) clearTimeout(timeOut);
timeOut = setTimeout(function() {
fun.apply()
}, delay);
}
}
app.onmousemove=debounce(fun,100)
</script>
效果:
解决指针问题和event对象
利用箭头函数,解决this指向问题
<script>
let app = document.getElementById("app");
function fun(ev) {
app.innerHTML = parseInt(app.innerHTML) + 1;
}
function debounce(fun, delay) {
let timeOut;
return function() {
let args = arguments;
if (timeOut) clearTimeout(timeOut);
timeOut = setTimeout(()=> {
fun.call(...args);
}, delay)
}
}
app.onmousemove = debounce(fun, 100)
</script>
这里基本就实现了防抖,下定义:
防抖的含义就是让某个时间期限(如上面的100毫秒)内,事件处理函数只执行一次。
大招后摇
我们实现防抖的方式实在计时结束后执行函数,可是很多时候,也会要求我们立刻触发事件执行,等待一些事件后次才能继续执行这个事件。说白了,就相当于我们的技能有后摇,用完技能要等一会才能触发事件。
我们的解决方法是传递一个参数进去要求我们立刻执行,此时,函数的执行不包含在计时器内部,而是执行玩函数后,给定一个计时器用于作为大招后摇。
<script>
let app = document.getElementById("app");
function fun(ev) {
app.innerHTML = parseInt(app.innerHTML) + 1;
}
function debounce(fun, delay, immediate) {
let timeOut;/*闭包返回定时器*/
return function() {
that = this; /*解决指针问题*/
args = arguments; /*解决event时间问题*/
if (timeOut) clearTimeout(timeOut);/*清除正在运行的计时器*/
if (immediate) {
/*判断是否立即执行*/
let playNow = !timeOut;/*false*/
timeOut = setTimeout(function() {
timeOut = null;
}, delay) /*添加一个计时,不进行直接操作*/
if (playNow) {
console.log(playNow)
fun.call(this, ...args)
}
} else {
timeOut = setTimeout(function() {
fun.call(that, ...args);
}, delay)
}
}
}
app.onmousemove = debounce(fun, 1000,true)
</script>
补充:返回值和取消操作
如果我们执行的事件是拥有返回值的,且必要的情况下,我们需要直接取消掉本次计时器,开始新的一轮。
<script>
function debounce(func, wait, immediately) {
let timer /*闭包返回计时器*/
let debounced = function (...args) { /*闭包返回防抖器*/
let result /*执行函数的返回值*/
// 清除闹钟后,闹钟还是存在的
if (timer) clearTimeout(timer) /*进入该分支语句,说明当前正在一个计时过程中,并且又触发了相同事件。所以要取消当前的计时,重新开始计时*/
if (immediately) { /*判断是否立即执行*//*理解为技能后摇*/
let called = !timer /*false*/
timer = setTimeout(() => {
timer = null
}, wait)/*添加一个计时,不进行直接操作*/
if (called) {
result = func.apply(this, args) /*包装成返回值*/
}
} else {/*理解为技能前摇蓄力*/
timer = setTimeout(() => {
func.apply(this, args)
}, wait)
}
return result
}
debounced.cancel = function () {
clearTimeout(timer)
timer = null
}/*触发取消操作,直接取消本次计时器 ,开启新的一轮*/
return debounced
}
</script>