在开发中经常碰到需要基于监听滚动条、窗口放大缩小、鼠标移动的需求,应该都能发现如果不加防抖或是节流函数,会使得这个监听器频繁的触发,可能会导致失帧的情况
下面会一步步的渐进式的去实现防抖功能,原理其实很简单
- 页面:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style type="text/css">
*{
margin: 0px;
padding: 0px;
}
#box{
width: 100%;
line-height: 30vh;
background-color: #B7ACAC;
text-align: center;
}
</style>
</head>
<body>
<div id="box">
0
</div>
</body>
</html>
先看一下没使用防抖时触发的频率
- js:
<script>
var num = 0;
var dom = document.getElementById("box");
function mouse() {
num ++;
dom.innerText = num;
}
dom.addEventListener("mousemove", mouse);
</script>
可以看到触发的频率是很频繁的
接下来按着需求一步步实现防抖函数
第一步实现 ,当鼠标移动时无论怎么移动都不触发,停止移动一秒后再触发
dom.addEventListener("mousemove", debounce(mouse, 1000));
function debounce(func, wait) {
var timer;
return function () {
clearTimeout(timer)
timer = setTimeout(func, wait);
}
}
这一步应该很好理解,利用闭包缓存timer变量,每次触发都会生成一个计时器任务使用timer变量保存定时器的ID并且清除这个任务,停止移动时就只剩下一个定时器任务
看下效果,很赞
立即执行版的防抖函数
这个版本就是我一移动就直接执行函数,继续移动是不管用的,必须要停下一定的时间是再移动才触发
可能有一些提交表单的按钮也会需要用到,按了之后立即提交表单验证,再触发必须要等一段时间,当然有验证的话按完清空表单或者禁用按钮都是可选的
dom.addEventListener("mousemove", debounce(mouse, 1000));
function debounce(func, wait) {
var timer;
return function () {
if(timer) clearTimeout(timer);
if(!timer) func();
timer = setTimeout(() => {
timer = null;
},wait)
}
}
这里的执行原理:
- 当第一次执行防抖函数
if(timer) clearTimeout(timer);
这时timer还是undefined 所以不会执行清除语句 if(!timer) func();
第一次执行timer是undefined在前面加上逻辑非!就变为true直接执行该语句,也就是执行函数- 执行定时器并给 timer赋值,这个值是定时器任务的id,先说鼠标一直移动的情况:
当鼠标一直移动时,timer是有值的,第二次触发
if(timer) clearTimeout(timer);
会取消对应的定时器任务,这里需要注意的一点是虽然任务被取消但是timer变量保存的还是该任务的id。
所以不会执行if(!timer) func();
,因为timer是有值的
接下来继续添加计时器任务,并给timer变量赋上相对应的任务id
再说一下鼠标不移动时的执行情况
当鼠标不在移动的情况下,这是定时器任务执行下一个,这个任务会在指定的时间下给timer赋值null
再次移动是就会像上面说的一样立即执行
看下效果,是我们要的没错了
结合一下两个模式,根据传第三个参数来按条件执行
dom.addEventListener("mousemove", debounce(mouse, 1000, false));
function debounce(func, wait, immediate) {
var timer;
return function () {
if(timer) clearTimeout(timer);
if(immediate){
if(!timer) func();
timer = setTimeout(() => {
timer = null;
},wait)
}else{
timer = setTimeout(func, wait);
}
}
}
是不是超级简单
绑定this,event
但接下来还有问题就是this的指向、event对象问题,现在的this是指向window的,现在我们来改变一下this指向和event对象,肯定是使用apply啦
function mouse(e) {
num ++;
dom.innerText = num;
console.log(this)
console.log(e.target)
}
dom.addEventListener("mousemove", debounce(mouse, 1000, false));
function debounce(func, wait, immediate) {
var timer;
return function () {
var that = this, ags = arguments;
if(timer) clearTimeout(timer);
if(immediate){
if(!timer) func.apply(that, ags);
timer = setTimeout(() => {
timer = null;
},wait)
}else{
timer = setTimeout(() => {
func.apply(that, ags)
}, wait);
}
}
}
这样就大功告成了,如果还需要其他的自己也知道该怎么加了,apply会立即执行函数的哟