前言:这里涉及许多其他知识,不在赘述自行查找其他资料学习,本文只谈防抖和节流的理解。
一、防抖
理解:打个比方,在现实生活中,电梯门的开关和防抖的功能很类似,当第一个人进入电梯后,如果后面没有人了那么电梯就会在5s内关门,如果后面陆续有人进入,每进一个人电梯都会重新计时5s直到最后一个进来的人,这时电梯倒计时5s就可以关门了。按照这个思路,设计以下防抖函数:
1 function debounce(fn, delay) { 2 let timer = null; 3 return function() { 4 if (timer) { 5 clearTimeout(timer); 6 } 7 let _this = this, 8 args = arguments; 9 10 timer = setTimeout(() => { 11 fn.apply(_this, args); 12 }, delay) 13 14 } 15 }
其中 fn 是我们需要控制的函数,delay是延迟的时间间隔。
只要触发防抖函数,clearTimeOut就会将timer清空,这就像电梯内有人进入就需要重新计时一样,直到最后一个人进入,再计时最后一遍,电梯关门,所以清空timer之后我们需要给timer重新设置一个新的计时器,来模拟最后一个上电梯的人,当最后一次计时结束setTimeOut里的回调函数就会执行,从而达到和电梯这样类似的防抖效果。
这种防抖有个缺点就是他会等到最后一个人进入才会关门(也就是说这种防抖不会立即执行待执行的函数),如果只一个人的情况下他还是会计时等待5s再关门。
思考:那么电梯的关门能否改进一下呢?:如果只有一个人进入电梯,那么就立马关门,不再等待。如果后续有人进入的话再设置等待,也就是防抖。
所以按照以上的问题改进以下防抖函数如下:
1 function debounce(fn, delay, triggleNow) { 2 let timer = null; 3 return function() { 4 if (timer) { 5 clearTimeout(timer); 6 } 7 let _this = this, 8 args = arguments; 9 if (triggleNow) { 10 let excute = !timer; //判断是否立即执行 11 timer = setTimeout(() => { 12 timer = null 13 }, delay); 14 if (excute) { 15 fn.apply(_this, args); 16 } 17 } else { 18 timer = setTimeout(() => { 19 fn.apply(_this, args); 20 }, delay) 21 } 22 } 23 }
这样的防抖函数就可以解决刚刚的问题了,加了一个triggleNow这个标志量,可以让用户决定到底要不要立马触发需要被执行的函数。
然而添加了triggleNow标志量后,我们不能直接通过这个标志来判断是否要直接执行fn函数,而是再设置一个标志量excute来判断,这样的好处是可以使代码逻辑和triddle为false时的逻辑保持一致,否则会非常麻烦。如果triggleNow为true的话,那么我们需要对逻辑稍作调整,因为这次我们希望如果第一个人进入电梯之后,立马关门(只触发一次事件时立马执行fn函数),所以我们需要第一次的时时候先执行一次fn,注意if(triggleNow)里的fn函数是否执行是靠excute变量来控制的,所以我们要保证第一次进来的时候excute的值为true,这样就会立马执行fn函数,在后续的过程中继续展现防抖的效果,于是乎在此之前我们需要将timer设置为null,但是不可以直接将timer设为null,而是要设置一个定时器来完成,为什么要样?你想啊,如果直接设为null 那么每次触发防抖函数,excute的值都会为true,fn函数每次都会执行,这样就失去了防抖的功能了,但是通过定时器来设置的话就不一样了,因为timer被设定为了一个新的定时器 ,这个定时器没clearTimeOut之后还会有它的id值,不为null,所以excute的值为false,这样在我们快速点击触发防抖函数时就不会执行fn函数,直到设置的设个定时器倒计时delay毫秒后,timer的值才为null,这样下一次点击才会执行fn,从而实现防抖功能。