一、防抖
什么是防抖?
有这样一种情况,想象有一个表单,点击提交按钮就发送请求给服务器。如果用户在很短的时间间隔内“手抖”点击了多次,又或者是恶意点击,那么就将发送多个请求。 该行为将造成服务器额外的不必要负载。
所谓防抖,实际上就是是处理这种常见的情况的描述。
实验探究
<body>
<button>Submit</button>
<script>
const btn = document.querySelector('button');
btn.onclick = function(){
console.log("send a request...")
}
</script>
</body>
该段代码,当点击Submit按钮的时候,将会触发onclick事件一次,多次点击,将多次触发。
为了解决该问题,核心就是使用一个setTimeout 定时器。
<body>
<button>Submit</button>
<script>
const btn = document.querySelector('button');
let timer = null;
btn.onclick = function(){
clearTimeout(timer)
timer = setTimeout(()=>console.log("send a request..."),500)
}
</script>
</body>
这就是一个基本防抖的实现。如果不容易看明白,下面我们详细的分析。
为了便于理解,我们先将第7行注释掉:
const btn = document.querySelector('button');
let timer = null;
btn.onclick = function(){
//clearTimeout(timer)
timer = setTimeout(()=>console.log("send a request..."),500)
}
我们定义了一个全局变量timer
用于预存setTimeout
实例对象,初始值设定为null
。
当onclick
函数被触发一次时,函数体内timer定时器立即被触发,将在500毫秒后执行console打印。
如果点击多次,由于setTimeout的异步执行特点,将会开启多个setTimeout 实例,各自的延时到500毫秒之后,依次执行打印。
打印完毕后,timer被销毁。
现在取消第7行注释:
const btn = document.querySelector('button');
let timer = null;
btn.onclick = function(){
clearTimeout(timer)
timer = setTimeout(()=>console.log("send a request..."),500)
}
我们以两次间隔较短的点击为例:第一次点击时,timer为null,clearTimeout(timer)即clearTimeout(null), 这并不影响,不会报错。 然后开启了一个setTimeout定时器A,等候500毫秒后执行。 但是,如果在间隔小于500毫秒内,第二次点击,定时器A 还在等候,但是我触发onclick事件时,直接就将定时器timer,即定时器A清除了,所以定时器A内的打印就不会执行,然后开启一个新的定时器B。
就这样,只要我在小于定时器中定义的时间间隔内重复点击,那么之前的定时器都将被清除掉。始终执行最后一个定时器。最大的外征特点就是,狂点按钮,但是只有松手的时候,最后那一次点击有效。
以上,就是所谓的防抖,并不复杂,但是确实很巧妙。
应用场景
防抖的应用场景主要是为了不让一些行为频繁触发
- keyup
- scroll
- resize
- mousemove
- 联想搜索建议
- ...
二、 节流
同样是为了避免频繁触发某个行为,还有一种实现方式,它和防抖的实现有所区别。
什么是节流?
关于节流,我想到了一个绝佳的例子。 就是技能cd。 如果玩过无限火力小黄毛就更容易理解了。Q键恨不得不松开。
有时候,我们希望我们能够多次触发某个行为,但是又不能让他没有间隔的疯狂触发。 那么这时候的解决方案,就叫做节流。
我希望我在技能cd好了的时候,立即可以使用该技能。 但是总不能让你无限光速连Q吧, 那叫bug。
实验探究
实际上,节流的实现也很简单。
<button>Submit</button>
<script>
const btn = document.querySelector('button');
let trigger = true;
btn.onclick = function() {
if (trigger) {
trigger = false;
console.log("do something...")
setTimeout(() => {
trigger = true;
}, 1000)
}
}
</script>
简单的来讲,就是加上一个自动“开关”。
我们预先定义了一个全局的变量trigger;当点击Submit 按钮,onclick 被触发。 然后依据trigger的值,来决定是否继续执行if
then块中的逻辑。
首次点击,由于trigger的预定义值为true,所以if
then 块中的逻辑必定会被执行。但是一旦进入块,立即将trigger的值改作false (这样就实现了关闭了if的块逻辑执行的“开关”。),执行一次任务后,我们通过setTimeout 埋一个定时器,1秒后执行,其任务就是将“开关”再次打开。 那么第二次点击Submit按钮的时候, 如果定时器超过了一秒,“开关”被打开,即trigger为true,才会执行,if
then 逻辑块中的任务。 否则就得等待“开关”被打开。
这样,通过“开关”的控制,就能实现,即便连续点击,但是也只能按照指定的间隔时间去触发任务。 这就是节流。
应用场景
节流的应用场景是,依然需要让它频繁触发,但是要有规则的频繁触发。 不能让它无缝连续触发。
- keyup
- scroll
- resize
- mousemove
- 联想搜索建议
- ...