浏览器的scroll(滚动条滚动)、keypress(按动按键)、mousemove(鼠标移动)等等事件在出发时,都是会不断的调用绑定在事件上的回调函数高频触发,如果回调函数复杂就会导致响应跟不上触发,有可能会造成页面的卡顿,极大地浪费资源,降低前端的性能。
对此有两种解决方案:防抖(debounce ) 和 节流(throttling );
一、 防抖(debounce )
作用:对在短时间内多次触发事件的回调函数,只执行最后一次,或者只在最开始时执行。
以用户拖拽改变窗口大小,触发resize事件为例,在这过程中窗口的大小一致在改变,所以如果我们在resize事件中绑定回调函数,这个函数将会在拖拽的过程一直触发,如果回调函数过于复杂难免不会造成页面卡顿,而且这种情况在多数情况下也是无意义的,还会造成资源的大量浪费。
普通方案:
window.addEventListener('resize',()=>{
console.log('触发事件');
});
优化方案
function debounce(fn,delay){
// 维护一个 timer
let timer=null;
return function(){
// 获取函数的作用域和变量
let context=this;
let args=arguments;
clearTimeout(timer);
timer=setTimeout(function(){
fn.apply(context,args);
},delay);
}
}
function res(){
console.log('触发事件');
}
//在 debounce 包装要触发的函数,过1000秒触发;
window.addEventListener('resize',debounce(res,1000));
代码逻辑:
- 在resize事件上绑定处理函数,这时debounce函数会立即调用,实际上绑定的函数时debounce函数内部返回的函数。
- 每一次事件被触发,都会清除当前的timer定时器然后重新设置,到时间调用。
- 只有在最后一次触发resize事件,才能在delay时间后执行。
添加参数,设置为立即执行函数
function debounce(fn,delay,rightAway=false){ //rightAway 不传时 ,默认为非立即执行函数
// 维护一个 timer
let timer=null;
return function(){
// 获取函数的作用域和变量
let context=this;
let args=arguments;
if(timer) clearTimeout(timer);
if(rightAway){
var now=!timer;
timer=setTimeout(()=>{
timer=null;
},delay);
if(now){
fn.apply(context,args);
}
}else{
timer=setTimeout(()=>{
fn.apply(context.args);
},delay);
}
}
}
二、节流(throttling )
作用:类似于防抖,不过节流是在一段时间之内只允许该函数执行一次
定时器实现
function throttle(fn,delay){
var timer =null;
return function(){
let context=this;
let args=arguments;
if(!timer){
timer=setTimeout(()=>{
fn.apply(context,args);
timer=null;
},delay);
}
};
}
function res(){
console.log('触发事件');
}
window.addEventListener('resize',throttle(res,2000));
时间戳实现
function throttle(fn,delay){
var p =Date.now();
return function(){
let context=this;
let args=arguments;
let n=Date.now();
if(n-p>=delay){
fn.apply(context,args);
p =Date.now();
}
};
}
function res(){
console.log('触发事件');
}
window.addEventListener('resize',throttle(res,2000));
以上两重情况都是用于高频触发事件,根据不同场景选择合适的用法。对于有停顿的高频触发事件建议选择防抖,然而对于高频触发并且连续的事件,选择节流。