1. 事件高频触发的场景
在JavaScript编程中,如果某个事件在短时间内被高频率的触发,可能就会出现对应的回调函数响应速度跟不上其调用频率的问题,进而会导致页面延迟,假死或卡顿的现象。这样会极大的浪费系统资源,同时糟糕的UI带来的用户体验也是很差的。
函数防抖(debounce)和函数节流(throttle)就是我们在这类场景下常用的优化手段,从而达到节约系统资源,改善用户体验的目的。
2. 原理解析
函数防抖(debounce)和函数节流(throttle)技术最终都是要限制(减少)函数的执行频次。具体的实现原理也很简单:巧妙地使用 setTimeout
来存放待执行的函数,这样可以很方便的利用 clearTimeout
在合适的时机来清除待执行的函数。
- 函数防抖(debounce) —— 一定时间内,只会执行最后一次函数回调;
- 函数节流(throttle) —— 一定时间内,只执行一次函数回调;
3. 函数防抖的适用场景举例 —— 搜索查询
函数防抖(debounce)的完整描述为:高频事件触发后,经过一定的时间间隔才会执行函数回调;如果事件在时间间隔内又被触发,则会重新计时,即只会执行最后一次的函数回调。
最常见的场景就是搜索查询。如果不做防抖的话,用户每输入一个字符就会发送一次请求,势必会给服务器造成不小的压力,进而影响系统性能。比如我们简单模拟一下:
//HTML
<input type="text">
//JavaScript
<script>
window.onload = function () {
function inputTap(e) {
console.log(e.target.value)
}
document.addEventListener('input', inputTap)
}
</script>
此时我们通过给input输入框绑定inputTap
事件并监听其输入,可以看到,每次我们输入一个字符,都触发了inputTap
事件。为了改善优化这一场景,就可以使用防抖技术,简单封装一个防抖函数:
具体原理是使用到了setTimeout
定时器函数,在定时器函数timer
定义之前加一步clearTimeout
清除定时器的操作。
/**
* @param {*} fn 回调函数
* @param {*} delay 延迟时间
*/
const debounce = function (fn, delay) {
let timer = null;
return function () {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, arguments)
}, delay);
};
};
这样每次事件触发后,虽然都定义了定时器函数timer
,但是如果下次操作来的比较快,上次定义的timer
还没来得及执行就被清除了;只有当两次操作的时间间隔大于设定值时,timer
才能正常执行。
比如我们设定用户完成输入操作后,间隔1000毫秒再调用inputTap
函数:
function inputTap(e) {
console.log(e.target.value)
}
document.addEventListener('input',debounce(inputTap,1000))
4. 函数节流的适用场景举例 —— 长列表滑动刷新
函数节流(throttle)就是指连续触发事件但是在一定时间内只执行一次函数,节流会稀释函数的执行频率。对于节流技术,一般有两种方式可以实现:时间戳版和定时器版。
这里我们就以长列表滑动刷新的场景为例:监听页面滚动并持续输出一个逐渐累加的num值。
//HTML
<div class="box" style="background: tomato;height: 700px;"></div>
<div class="box" style="background: skyblue;height: 700px;"></div>
<div class="box" style="background: red;height: 700px;"></div>
<div class="box" style="background: yellow;height: 700px;"></div>
<div class="box" style="background: yellowgreen;height: 700px;"></div>
//JavaScript
<script>
window.onload = function () {
let num = 0
function scrollTap() {
num++
console.log(`num: ${num}`)
return num
}
document.addEventListener('scroll', scrollTap)
}
</script>
这样的页面滚动刷新很有可能会造成页面的卡顿,进而影响性能。所以就可以使用函数节流(throttle)的技术来改善优化。
① 时间戳版的节流函数:
原理就是分别记录两次操作的时间戳,并判断两者的时间间隔是否大于设定值,只有大于设定值时才会调用回调函数。
const throttle = function (fn, delay) {
let preTime = Date.now()
return function () {
let doTime = Date.now()
if (doTime - preTime >= delay) {
fn.apply(this, arguments)
preTime = Date.now()
}
}
}
② 定时器版的节流函数:
原理是使用了setTimeout
来定义了一个计时器函数timer
,timer
每次执行完都会清除自身(timer = null
),这样下次才能进入if
分支,重新定义timer
函数来调用回调函数。
const throttle = function (fn, delay) {
let timer = null
return function () {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, arguments)
clearTimeout(timer)
timer = null // 敲重点
}, delay);
}
}
}
当然两种方法达到的效果是一样的,可以明显的对比出:这次输出的数值就少的多了。从而实现了减少回调函数的执行次数的目的。
参考文章:https://blog.csdn.net/qq_36818627/article/details/108023381
每一个不曾起舞的日子,都是对生命的辜负!
—— 尼采