编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

前端性能优化:事件的节流throttle与防抖debounce

wxchong 2024-10-15 17:01:06 开源技术 13 ℃ 0 评论

scroll 事件是一个非常容易被反复触发的事件,另外,resize 事件、鼠标事件(比如 mousemove、mouseover 等)、键盘事件(keyup、keydown 等)都存在被频繁触发的风险。

频繁触发回调导致的大量计算会引发页面的抖动甚至卡顿。为了规避这种情况,我们需要一些手段来控制事件被触发的频率。就是在这样的背景下,throttle(事件节流)和 debounce(事件防抖)出现了。

闭包形式实现,即返回的是一个函数)

throttle:

// fn是我们需要包装的事件回调, interval是时间间隔的阈值
function throttle(fn, interval) {
 // last为上一次触发回调的时间
 let last = 0
 // 将throttle处理结果当作函数返回
 return function () {
 // 保留调用时的this上下文
 let context = this
 // 保留调用时传入的参数
 let args = arguments
 // 记录本次触发回调的时间
 let now = +new Date()
 // 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
 if (now - last >= interval) {
 // 如果时间间隔大于我们设定的时间间隔阈值,则执行回调
 last = now;
 fn.apply(context, args);
 }
 }
}
// 用throttle来包装scroll的回调
const better_scroll = throttle(() => console.log('触发了滚动事件'), 1000)
document.addEventListener('scroll', better_scroll)

Debounce:(有定时器)

// fn是我们需要包装的事件回调, delay是每次推迟执行的等待时间
function debounce(fn, delay) {
 // 定时器
 let timer = null
 // 将debounce处理结果当作函数返回
 return function () {
 // 保留调用时的this上下文
 let context = this
 // 保留调用时传入的参数
 let args = arguments
 // 每次事件被触发时,都去清除之前的旧定时器
 if(timer) {
 clearTimeout(timer)
 }
 // 设立新定时器
 timer = setTimeout(function () {
 fn.apply(context, args)
 }, delay)
 }
}
// 用debounce来包装scroll的回调
const better_scroll = debounce(() => console.log('触发了滚动事件'), 1000)
document.addEventListener('scroll', better_scroll)

用 Throttle 来优化 Debounce

debounce 的问题在于它“可以一等再等”。试想,如果用户的操作十分频繁——他每次都不等 debounce 设置的 delay 时间结束就进行下一次操作,于是每次 debounce 都为该用户重新生成定时器,回调函数被延迟了不计其数次。频繁的延迟会导致用户迟迟得不到响应,用户同样会产生“这个页面卡死了”的观感。

为了避免弄巧成拙,需要借力 throttle 的思想,打造一个“有底线”的 debounce——等待可以,但必须有原则:delay 时间内,可以为不断重新生成定时器;但只要delay的时间到了,必须要给用户一个响应。

// fn是我们需要包装的事件回调, delay是时间间隔的阈值
function throttle(fn, delay) {
 // last为上一次触发回调的时间, timer是定时器
 let last = 0, timer = null
 // 将throttle处理结果当作函数返回
 return function () { 
 // 保留调用时的this上下文
 let context = this
 // 保留调用时传入的参数
 let args = arguments
 // 记录本次触发回调的时间
 let now = +new Date()
 // 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
 if (now - last < delay) {
 // 如果时间间隔小于我们设定的时间间隔阈值,则为本次触发操作设立一个新的定时器
 clearTimeout(timer)
 timer = setTimeout(function () {
 last = now
 fn.apply(context, args)
 }, delay)
 } else {
 // 如果时间间隔超出了我们设定的时间间隔阈值,那就不等了,无论如何要反馈给用户一次响应
 last = now
 fn.apply(context, args)
 }
 }
}
// 用新的throttle包装scroll的回调
const better_scroll = throttle(() => console.log('触发了滚动事件'), 1000)
document.addEventListener('scroll', better_scroll)

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表