一、防抖函数:
当我们的回调函数(尤其是异步操作,比如请求数据)在短时间内,被连续调用时,会出现卡顿现象,此时的用户体验下降和性能消耗增大;
所以需要添加防抖函数来解决:在设定的事件间隔,n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时,只执行最后一次操作;这样可以减少对性能的消耗;
可以使用的插件比较多;比如underscore,lodash
lodash中文文档https://www.lodashjs.com/underscore中文文档
https://www.underscore-js.com/
二、使用案例
下面简单用计数器演示下,防抖的操作;
当在连续点击计数按钮时,不会连续触发计数;
//css代码
<style>
div {
width: 500px;
height: 500px;
background-color: pink;
font-size: 1.25rem;
font-weight: 700;
text-align: center;
line-height: 500px;
}
</style>
<body>
<div id="container"></div>
<button>点我</button>
<script src="./underscore/underscore.js"></script>
<script>
let count = 0;
let container = document.querySelector('#container');
let button = document.querySelector('button')
function countNum(e) {
count++;
container.innerHTML = count;
}
button.onclick = _.debounce(countNum, 300, true);
</script>
</body>
三、底层原理简单表示
1.debounce函数的参数有三个:
debounce(func,wait,immidiate);
(1) func 表示事件执行的回调函数;
(2)wait 表示事件延迟执行的等待时间;
(3) immediate 表示的是是否立即执行,参数为true时,表示立即执行;为false时,表示在wait等待时间过后执行;
下面主要对三个参数来分析实现功能:
(1)延时功能使用 settimeout 来实现;
(2)this指向问题,在插件中,回调函数的this指向是指向事件的触发者的;所以在做封装时,需要使用call或者apply来改变this指向问题(不改变的化,this是指向window的),因为debounce函数是事件执行的回调函数,所以在debounce中,this指向是指向事件调用者的,所以使用context来保存this,在返回的函数中,使用call来修改func函数的指向;
(3)event事件的问题,在插件中的event参数是事件的事件的具体类型;在debounce函数中的arguments中是保存了事件调用的信息,所以将argument的事件,使用call方法将参数传递给func;
function debounce(func, wait,immediate) {
let timeout;
return function () {
//在wait时间内,重复触发事件的时候,清除定时器,重新进入wait时间
// console.log(arguments);//arguments参数是具有事件对象的,所以直接将arguments参数传递给func函数
let args = arguments[0];
let context = this;
// console.log(this);//-->指向调用者
clearTimeout(timeout)
//当immediate参数为true时,是立即执行,不进入延迟操作,即进入第一个if判断分支
if (immediate) {
let callNow = !timeout;
timeout = setTimeout(() => {
timeout = null;
}, wait);
//immediate参数设置为true时,(1)刚开始进来timeout还没有生成,即为undefined,取反为true;就会进入下面的立即调用func函数,即立即执行,延时函数开始计时,生成了一个timeout,所以在wait时间内,再次触发事件的时候,callnow不再是true(因为生成了一个timeout,不再是undefined,取反为false),当wait时间过后,重置了timeout = null,所以wait时间后,再次触发事件时,callNow为true(因为timeout置为null了)
if(callNow)func.call(context,args)
} else {
//当immediate参数是false时,不进入延迟操作函数
timeout = setTimeout(function () {
//修改this指向,使用call方法
func.call(context,args)
}, wait);
}
}
}