技术背景:
相信以下场景你都不陌生
- 多次点击按钮导致页面失去响应或者出现意外情况。
- 如何实现搜索联想功能以及各企业邮箱提示功能。
- 页面滚动、输入框输入以及窗口尺寸变化频繁触发事件。
- 手机号、邮箱格式的实时校验
- …
为了解决或者实现这类场景,优化性能和改善用户体验。衍生出了一种技术,防抖(debouce)和节流(throttle)
一、概念
- 防抖(debounce)
延迟执行函数,直到一段连续的时间内没有新的事件被触发。
解释:当一个事件在一段时间内连续触发时,只有最后一次触发且经过一定时间后才会执行相应的处理函数
类比:王者荣耀的回城效果,多次点击回城会一直重置回城时间,只有停止点击回城并等待一段时间后才会实现回城功能。
具体来说,当触发一个事件时,防抖函数会启动一个定时器,延迟一段时间执行事件处理函数。如果在这段时间内又触发了同样的事件,就会清除之前的定时器,并重新设置一个新的定时器。只有在最后一个事件触发后,经过一段时间内没有新的事件再次触发时,事件处理函数才会被执行。
- 节流(throttle)
确保函数在一定时间间隔内最多执行一次,无论事件触发频率如何。
解释:在一段时间内,无论事件触发频率如何,处理函数能且仅能触发一次。
类比:王者荣耀中,在技能的CD周期内,技能仅能触发一次,而且是从第一次触发时开始执行。
具体来说,当触发一个事件时,节流函数会立即执行该事件处理函数,然后在设定的时间间隔内,如果再次触发相同的事件,节流函数会忽略该事件,直到这段时间间隔过去,才会重新触发执行。
二、手写实现
- 防抖(debounce)
<html>
<button>打印</button>
<script>
/*
******封装防抖工具函数 myDebouce *******
*/
// fn 需要添加防抖效果的函数(回调函数),delay 延迟时间
const myDebounce = (fn,delay = 500)=>{
// 存储上一次的延时器
let timer;
return function (...args) {
// 清除上一次的延时器
clearTimeout(timer);
// 重置新的延时器
timer = setTimeout(()=>{
fn(...args)
},delay)
}
}
// 定义处理函数,并添加防抖效果
const click = myDebouce(function(message){
console.log(message);
},1000)
// 绑定点击事件
document.querySelector('button').addEventListener('click',function () {
click('防抖成功')
})
</script>
</html>
如果一直点击按钮,事件不会触发,停止点击delay毫秒后,事件触发
- 节流(throttle)
方法一: 使用时间戳的写法
<html>
<button>打印</button>
<script>
/*
******封装节流工具函数 throttle*******
*/
// fn 需要添加节流效果的函数(回调函数), T 时间周期,T的默认值是500ms
const myThrottle = (fn,T = 500)=>{
// 储存上一次函数执行的时间戳
let old = 0
return function (...args){
// 获取当前时间戳
const now = new Date().getTime()
// 判断当前时间戳与上一次触发的时间戳差值是否大于时间周期
if( now - old >= T){
fn(...args);
// 更新上一次执行的时间戳
old = now;
}
}
}
// 定义处理函数,并添加节流效果
const click = myDebouce(function(message){
console.log(message);
},1000)
// 绑定点击事件
document.querySelector('button').addEventListener('click',function () {
click('节流成功!按周期,每秒打印一次,无论频率多高')
})
</script>
</html>
方法二: 使用定时器 setTimeout 的写法
// fn 需要添加节流效果的函数(回调函数), T 时间周期
const myThrottle = (fn,T)=>{
// 通过闭包保存一个 “节流阀”,默认为false
let flag = false
return function (...args){
// 节流阀存在,表示已经执行
if(flag){
return
}
else {
flag = true; //立刻将节流阀设置为true,表示处理函数正在执行
setTimeout(() => {
fn(...args)
//关键!!!! 执行完毕,重置节流阀为false
flag = false
}, T);
}
}
}
// 绑定事件
const click = myThrottle(function (message) {
console.log(message)
},1000)
// 绑定点击事件
document.querySelector('button').addEventListener('click',()=>{
click('节流成功')
})
如果一直点击按钮,控制台会按T的时间周期一直执行处理函数,在时间周期T内,函数仅能执行一次。
三、使用场景
- 防抖
- 避免用户重复点击按钮等需要网络请求的触发事件。
- 搜索框搜索输入,只需用户最后一次输入完,再发送请求。
- 调整浏览器窗口resize时,避免重复执行绑定的回调函数,浪费大量资源,降低性能。
- 手机号、邮箱号的格式验证。
- 节流
- 滚动加载,加载更多或滚到底部监听。
- 搜索框,搜索联想功能
- 浏览器播放事件,每个一秒计算一次进度信息等。
- 鼠标移动或者滚轮滑动等频繁触发的事件。
四、区别与联系
联系
- 都可以通过定时器setTimeout实现
- 都是用来限制函数执行频率的技术
区别
- 实现方式不同。
函数防抖是在一定时间内,如果事件重复触发,则只执行最后一次操作。函数节流则是在一定时间内,无论事件触发多少次,只会执行一次操作。
- 执行时机不同。
函数防抖执行时机在事件停止触发后一段时间后,而函数节流则是在指定时间周期内只会执行一次。
- 适用场景不同。
函数防抖适用于一些需要等待用户停止操作之后再执行的场景,如搜索输入框、窗口调整。函数节流适用于一些高频率触发的事件,如鼠标移动事件,滚动事件等。
总而言之,防抖和节流都是用来限制函数执行频率的技术