函数防抖和节流
引言
引入函数防抖和函数节流的目的是什么呢?
一些场景往往由于事件频繁被触发,因而频繁执行DOM操作、资源加载等重行为,导致UI停顿甚至浏览器崩溃。比如:
- window对象的resize、scroll事件
- 拖拽时的mousemove事件射击游戏中的mousedown、keydown事件
- 文字输入、自动完成的keyup事件
实际上对于window的resize事件,实际需求大多为停止改变大小n毫秒后执行后续处理;而其他事件大多的需求是以一定的频率执行后续处理。针对这两种需求就出现了debounce和throttle两种解决办法。
throttle(又称节流)和debounce(又称去抖)其实都是函数调用频率的控制器。
一.函数防抖(debounce)
- 防抖的定义:函数防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
- 让我们来举个例子:函数防抖的实际场景,就像现实生活中我们坐公交,司机需要等最后一个人进入,等待几秒才关门。每次进入一个人,司机就会重新等待几秒再关门,如果在这等待几秒的过程中,又有一个人进入,车不会发动,又会重新等待几秒。直到这几秒内无人再进入后,公交车才发动。
- 简单来说,函数防抖就是当一个事件不断触发时,它的效果函数只执行最后一次。
- 函数防抖的实现
function debounce(fun,wait){
let timer;
return function(){
clearTimeout(timer);
timer=setTimeout(fun,wait);
}
}
document.addEventListener('scroll', debounce(scrollTap, 500));
在这里的scrollTap函数是一个函数名,也就是我们在触发事件后想要进行的效果函数。调用debounce()函数会返回匿名函数,执行此匿名函数的内容。
函数防抖的实现过程就是,一触发事件,debounce函数里就会清除掉之前的定时器,然后再重新设置定时器。当连续触发事件时,就不断的清除定时器,一旦不再触发事件,最后一次触发的事件所绑定的定时器就会执行,延迟时间到达后呈现效果。
二.函数节流(throttle)
- 节流的定义:限制一个函数在一定时间内只能执行一次。
- 让我们来举个例子:函数节流的实际场景,就像是设置一个闹铃,让它每隔20分钟执行一次。但也可以让它每隔二十秒响一次,响铃时间为30秒,这样闹铃就不停的响。显然后一种情况是我们并不想要的,前者就像函数节流一样,在一定的时间段内只执行一次。
- 简单来说,函数节流就是当一个事件不断触发时,在一定时间内会执行一次。
- 函数节流的定时器实现
function throttle(fun,wait){
let timer;
return function(){
if(timer){
return;
}
else{
timer=setTimeout(function(){
fun();
timer=null;
},wait);
}
}
}
document.addEventListener('scroll', throttle(scrollTap, 500));
函数节流定时器版的实现,就是当调用throttle函数时,当有定时器在执行时,就直接返回,定时器中的效果函数执行完毕后再重新启动下一次的定时器。这里直接返回达到了延迟事件的目的。
- 函数节流的时间戳实现
function throttle2(fun,wait){
let last=Date.now();
return function(){
let now=Date.now();
if(now-last>=wait){
fun();
last=now;
}
else{
return;
}
}
}
document.addEventListener('scroll', throttle2(scrollTap, 500));
函数节流时间戳的实现,就是在通过Date.now()方法创建一个上一次触发事件时间,然后再创建一个当前触发事件的时间,如果这两个的时间间隔不小于延迟时间才执行效果函数,否则不进行任何操作。
三.异同点
异同点 | 函数防抖 | 函数节流 |
---|---|---|
相同点 | 目的都是,降低回调执行频率。节省计算资源。 | 实现方法都可以用定时器来实现 |
不同点 | 侧重一定时间连续触发,只在最后执行一次, | 侧重于一段时间内只执行一次。函数节流定时器版中强调的是在一个定时器执行的过程中,不能有别的定时器对它造成干扰。而时间戳版中也是在给定时间内才执行一次方法。也就是说可以有多次的效果函数的实现。 |
四.小练习
在了解了函数节流和函数防抖方面的内容后,我做了一个比较简单的练习。
练习:想要用四个点击按钮来分别展示添加函数防抖后的效果函数,添加函数节流定时器版后的效果函数,添加函数节流时间戳版的效果函数以及普通的效果函数的不同效果。
我的实现:通过分别给这四个按钮绑定点击事件,然后分别在控制台上打印不同的字样来区分这四种效果。
具体代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style type="text/css">
* {
padding: 0;
margin: 0;
}
</style>
</head>
<body>
<button style="width:100px;height:50px;">函数防抖定时器</button>
<button style="width:100px;height:50px;">函数节流定时器</button>
<button style="width:100px;height:50px;">函数节流时间戳</button>
<button style="width:100px;height:50px;">这是普通效果!!</button>
<script>
var buttonAll=document.getElementsByTagName('button');
var len=buttonAll.length;
buttonAll[0].addEventListener('click',debounce(function(){
console.log('我是函数防抖');
},500),false);
buttonAll[1].addEventListener('click',throttle(function(){
console.log('我是函数节流定时器版');
},500),false);
buttonAll[2].addEventListener('click',throttle2(function(){
console.log('我是函数节流时间戳版');
},500),false);
buttonAll[3].addEventListener('click',function(){
console.log('我是普通效果');
},false);
//函数防抖
function debounce(fun,wait){
let timer;
return function(){
clearTimeout(timer);
timer=setTimeout(fun,wait);
}
}
//函数节流定时器
function throttle(fun,wait){
let timer;
return function(){
if(timer){
return;
}
else{
timer=setTimeout(function(){
fun();
timer=null;
},wait);
}
}
}
//函数节流时间戳
function throttle2(fun,wait){
let last=Date.now();
return function(){
let now=Date.now();
if(now-last>=wait){
fun();
last=now;
}
else{
return;
}
}
}
</script>
</body>
</html>
五.疑问点
在对整屏翻动进行绑定事件时,由于整屏翻动用的mousewheel事件的效果函数中需要传入参数e(e是事件对象,就是js事件相关信息对象,而且根据不同的事件,属性值会不一样),而e.wheelDelta又是确定鼠标滚动方向的一个属性。这时直接将该效果函数作为形参放入
document.addEventListener(‘mousewheel’, debounce(funOne, 500));
中执行控制台会报错显示wheelDelta有误。这里又要怎么解决呢?