1 前言
在前端开发过程中,会遇到很多实时输入查询、滚动条触发等业务。而这些频发操作的事件,如果每次触发都进行执行的话,会造成性能下降、后台的压力变大,那么此时就需要使用防抖和节流进行处理。
防抖和节流,见名思义:防抖是防止抖动,节流是节约流量。具体释义如后面介绍。
2 防抖
防抖(Debounce) 指的是触发事件后n秒后才能执行函数,如果在n秒内触发了事件,则会重新计算执行时间。
常见场景:点击按钮、拍照、下拉触底加载下一页等。
如上图所示,持续触发输入事件时,并不会立即执行func函数,而是在指定时间delay中没有再次出发事件时,才会进行延时执行func函数。
2.1 原始栗子(未进行防抖处理)
为了更加深入透彻地理解为什么要进行防抖处理,我们可以先体验不进行防抖的输入函数触发ajax实时请求的情况。
<div class="box">
没有进行防抖处理的:<input type="text" id="name" name="name">
</div>
<script>
// 模仿一段ajax请求
function ajax(value){
console.log("ajax request: " + value + ", time: " + new Date());
}
const inputBox = document.getElementById("name");
inputBox.addEventListener("keyup",e=>{
ajax(e.target.value);
})
</script>
运行结果:
上面结果所示,只要我们在输入框中每次输入值、按下键盘,那么就会触发一次ajax请求,这对于用户和开发者而言都是不好的体验和资源的浪费。此时,我们想到每次用户输入文字都是需要一定时间的,那么我们可以定义在规定时间进行完整输入才能进行请求,这样我们可以减轻对后台的压力。
2.2 防抖栗子(非立即执行版本-倚天剑)
前面,我们看到对于短时间内频繁点击或输入的事件触发,未使用防抖处理的事件对于用户体验并不是很好。因此我们可以使用防抖进行处理,如下:
<div class="box">
进行防抖处理的:<input type="text" id="name" name="name">
</div>
<script>
// 模仿一段ajax请求
function ajax(value){
console.log("ajax request: " + value + ", time: " + new Date());
}
// 防抖函数
function debounce(func,delay){
let timeout; //定时器
return function(arguments){
// 判断定时器是否存在,存在的话进行清除,重新进行定时器计数
if(timeout) clearTimeout(timeout);//清除之前的事件
timeout = setTimeout(()=>{
func.call(this,arguments);//执行事件
},delay);
}
}
const inputBox = document.getElementById("name");
// 使用防抖函数进行封装ajax
let debounceAjax = debounce(ajax,500);
inputBox.addEventListener("keyup",e=>{
debounceAjax(e.target.value);
})
</script>
运行结果:
从上面的运行结果可以看出,在500ms内输入文字按下键盘都不会触发请求事件,而是在输入框的定时器500ms停止输入后发送请求。实现原理很简单,就是对于频繁输入的输入框请求事件添加定时器进行计数,在指定时间内进行频繁输入并不会进行ajax请求,而是在指定时间间隔内停止输入才会执行函数。当停止输入但在此定时器计数时间内,会重新进行触发请求事件。
2.3 防抖栗子(立即执行版-屠龙刀)
非立即执行版指的是触发事件后函数不会立即执行,而是在 n 秒后执行,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
立即执行版指的是触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果。
简而言之,立即执行版就是第一次事件触发后会立即执行,至少执行一次事件。
<div class="box">
进行防抖处理的:<input type="text" id="name" name="name">
</div>
<script>
// 模仿一段ajax请求
function ajax(value){
console.log("ajax request: " + value + ", time: " + new Date());
}
// 防抖函数
function debounce(func,delay){
let timeout; //定时器
return function(arguments){
// 判断定时器是否存在,存在的话进行清除,重新进行定时器计数
if(timeout) clearTimeout(timeout);
const flag = !timeout;//此处是取反操作
timeout = setTimeout(()=>{
timeout = null;
},delay);
// 触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果。
if(flag) func.call(this,arguments);
}
}
const inputBox = document.getElementById("name");
// 使用防抖函数进行封装ajax
let debounceAjax = debounce(ajax,500);
inputBox.addEventListener("keyup",e=>{
debounceAjax(e.target.value);
})
</script>
运行结果:
2.4 防抖栗子(综合版本-珠联璧合)
在开发过程中,会根据实际应用场景选择合适的防抖函数,那么进行屠龙刀和倚天剑合一使用,就能打败绝大数场景的防抖函数。让天下没有难处理的防抖函数,一个字,绝!
/*
func:要进行防抖处理的函数
delay:要进行延时的时间
immediate:是否使用立即执行 true立即执行 false非立即执行
*/
function debounce(func,delay,immediate){
let timeout; //定时器
return function(arguments){
// 判断定时器是否存在,存在的话进行清除,重新进行定时器计数
if(timeout) clearTimeout(timeout);
// 判断是立即执行的防抖还是非立即执行的防抖
if(immediate){//立即执行
const flag = !timeout;//此处是取反操作
timeout = setTimeout(()=>{
timeout = null;
},delay);
// 触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果。
if(flag) func.call(this,arguments);
}else{//非立即执行
timeout = setTimeout(()=>{
func.call(this,arguments);
},delay)
}
}
}
3 节流
节流(Throttle) 指的是连续触发事件但是在n秒中只执行一次函数。即不管你在指定时间内触发多少次函数,但是它只执行一次事件。(只有一次生效)
常见场景:即时查询
如上图所示,在持续进行触发输入事件时,并不会立即执行func的函数请求,而是每隔指定的delay时间后才会执行一次func函数,不管这段时间内你点击了多少次。
3.1 节流栗子(定时器版本-倚天剑)
所谓定时器版本,就是用定时器来进行计数实现节流。
<div class="box">
进行节流处理的:<input type="text" id="name" name="name">
</div>
<script>
// 模仿一段ajax请求
function ajax(value){
console.log("ajax request: " + value + ", time: " + new Date());
}
// 节流--定时器版
function throttle(func,delay){
let timeout;//定义一个定时器标记
return function(arguments){
// 判断是否存在定时器
if(!timeout){
// 创建一个定时器
timeout = setTimeout(()=>{
// delay时间间隔清空定时器
clearTimeout(timeout);
func.call(this,arguments);
})
}
}
}
const inputBox = document.getElementById("name");
// 使用节流函数进行封装ajax
let throttleAjax = throttle(ajax,500);
inputBox.addEventListener("keyup",e=>{
throttleAjax(e.target.value);
})
</script>
运行结果:
从上面可以看到,无论我们在输入框输入多少文字,在指定时间内只执行一次函数。
3.2 节流栗子(时间戳版本-屠龙刀)
其实就是利用时间戳来记录时间,如下:
// 节流--时间戳版本
function throttle(func,delay){
let prev = 0;//上一次记录时间
return function(arguments){
let now = Date.now();//当前时间戳
if(now - prev > delay){//当前时间 - 上次时间 > 延时时间
func.call(this,arguments);//执行函数 发送请求
prev = now;//重置记录时间
}
}
}
运行时间:
从上面可以看到,我们在设置时间为1s内,只进行执行函数一次。
4 小结
- 函数防抖和函数节流都是防止某一时间内频繁触发。
- 函数防抖是在指定时间只执行一次,而函数节流是每到指定间隔时间执行一次。
- 函数防抖是将几次操作合并为一此操作进行,函数节流使得一定时间内只触发一次函数。
简而言之
- 防抖:就相当于MOBA游戏中的回城操作,如果被打断需要重新点击回城技能,而每次回城需要指定的时间。
- 节流:就相当于MOBA游戏中的技能施法,每次使用完都会重新CD,只有CD完才能进行下一次施法。
应用场景
- 防抖
debounce
search
搜索联想,用户在不断输入值时,用防抖来节约请求资源。
window
触发resize
的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
- 节流
throttle
鼠标不断点击触发,mousedown
(单位时间内只触发一次)。
监听滚动事件,比如是否滑到底部自动加载更多,用throttle
来判断。
参考文章
写在后面
每个人走过的路看过的风景是不一样的,只有自己走过的路才属于自己的收获。对于同一件事情的认知和感悟,每个人不尽相同,都赋予了自己对于它的理解。同样的,对于防抖和节流,笔者在学习和理解时也会出现偏颇和误差,希望大家能够给予建议和指导。
写在最后
感谢大家的阅读,我将继续和大家分享更多优秀的文章,此文参考了大量书籍和文章,如果有错误和纰漏,希望能给予指正。
关注微信公众号【前端万有引力】,及时获取更多相关技术、面试经验等文章。