在JS的事件处理中,防抖与节流是一种很重要的处理函数执行方式的手段,比如一些会经常触发的事件(点击,下拉等),如果不加以控制,会对性能造成负担,或者出现BUG。
基本原理
通过settimeout的方式,在一定时间间隔内将多次触发变成一次触发,或者限制出发次数。
<body>
<input type="text">
<button type="submit" id="input">提交</button>
</body>
<script>
var btn = document.getElementById('input');
btn.addEventListener('click', submit, false);//没有使用防抖
/*需要执行的函数*/
function submit(e) {
console.log(this); //这里我们希望this指向到input
console.log('发送请求~');
console.log(e);
}
</script>
运行结果可想而知:(狂点。。。)
接下来我们一步步的实现防抖
<script>
var btn = document.getElementById('input');
//btn.addEventListener('click', submit, false);//没有使用防抖
btn.addEventListener('click', debounce(submit,2000), false);//使用防抖
/*需要执行的函数*/
function submit(e) {
console.log(this); //这里我们希望this指向到input
console.log('发送请求~');
console.log(e);
}
function debounce(fn,delay){
var time = null;
return function(){
if(time){
clearTimeout(time);//多次点击就会清除上一个定时器,所以始终按照最后一次的点击时间开始执行
}
time = setTimeout(() => {
fn();
}, delay);
}
}
</script>
运行结果:
这里点击一次两秒后执行,多次点击以最后一次的点击开始计时,但是我们发现,执行函数中输出的e,和this并不是我们想要的结果。
因为我们通过这种包装的方式实现防抖会影响到参数的取值作用域,所以这里我们在进行优化,解决这个问题。
解决this指向和e取值的问题:
<script>
var btn = document.getElementById('input');
//btn.addEventListener('click', submit, false);//没有使用防抖
btn.addEventListener('click', debounce(submit,2000), false);//使用防抖
/*需要执行的函数*/
function submit(e) {
console.log(this); //这里我们希望this指向到input
console.log('发送请求~');
console.log(e);
}
function debounce(fn,delay){
var time = null;
return function(...args){//这里可能传递的参数不止事件对象e一个,所以使用这种写法保证参数都传递进去
if(time){
clearTimeout(time);//多次点击就会清除上一个定时器,所以始终按照最后一次的点击时间开始执行
}
time = setTimeout(() => {//这里使用箭头函数使得内部的this为上级作用域中的this
fn.apply(this, args);//这里使用apply使得执行函数fn中的this指向当前作用域的this,而当前作用域中的this指向上级作用域中的this,上级作用域中的this又指向触发点击事件的dom元素,同时也将事件参数传递了进去
}, delay);
}
}
</script>
结果:
看似完美,但是还有一个问题,我们每次点击,无论多次点击还是只点击一次,都要等延时结束后才能执行,这在加上网络请求不好的情况下,会对用户造成不好的体验的。我们希望用户第一次点击的时候能立即执行。
接下来继续优化:
<script>
var btn = document.getElementById('input');
btn.addEventListener('click', debounce(submit, 2000), false);
function submit(e) {
console.log(this); //这里我们希望this指向到input
console.log('发送请求~');
console.log(e);
}
function debounce (fn, interval = 500) {
let timer, firstTime = true;
return function (...args) {
if (firstTime) {
fn.apply(this, args)
firstTime = false;
} else {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn.apply(this, args)
}, interval)
}
}
}
</script>
另外一种限制方法是节流了,连续多次点击,事件会限制次数的一直触发 的效果,和防抖有点区别,写法简单,主要利用了时间戳的方法进行限制。
<script>
var btn = document.getElementById('input');
btn.addEventListener('click', throttle(submit, 2000), false)
function submit(e) {
console.log(this); //这里我们希望this指向到input
console.log('发送请求~');
console.log(e);
}
function throttle(fn,delay){
var begin = 0;
return function(...args){
var cur = new Date().getTime();//获取当前的时间戳
if(cur - begin > delay){//点击(连续)触发事件大于了
fn.apply(this,args);
begin = cur;
}
}
}
</script>
当用户以很快的速度不停点击时,函数总是以规定的延时 间隔进行执行。这样就实现了节流效果