【JS】前端性能优化:防抖和节流
前言
关于防抖和节流
在进行窗口的 resize、scroll、输出框内容校验等操纵的时候,如果事件处理函数调用的频率无限制,会加重浏览器的负担,导致用户体验非常之差。那么为了前端性能的优化也为了用户更好的体验,就可以采用防抖(debounce)和节流(throttle)的方式来到达这种效果,减少调用的频率。
为什么滚动 scroll、窗口 resize 等事件需要优化
Web 页面展示经历的步骤:js—style—layout—paint—composite
滚动事件的应用很频繁:图片懒加载、下滑自动加载数据、侧边浮动导航栏等。
网页生成的时候,至少会渲染(Layout+Paint)一次。用户访问的过程中,还会不断重新的重排(reflow)和重绘(repaint),用户 scroll 行为和 resize 行为会导致页面不断的进行重新渲染,而且间隔频繁,事件中涉及到的大量的位置计算、DOM 操作、元素重绘等等这些都无法在下一个 scroll 事件出发前完成的情况下,容易造成浏览器卡帧。
防抖 Debounce
防抖情景
- 有些场景事件触发的频率过高(mousemove onkeydown onkeyup onscroll)如滚动监听,商品秒杀等
- 回调函数执行的频率过高也会有卡顿现象,可以一段时间过后进行触发,去除无用操作
1 | function showTop() { |
防抖原理
一定在事件触发 n 秒后才执行,如果在一个事件触发的 n 秒内又触发了这个事件,以新的事件的时间为准,n 秒后才执行,等触发事件 n 秒内不再触发事件才执行。
官方解释:当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。
“你别一直抖!你只要抖了我就不干活了,什么时候不抖了我再接着干。”
防抖函数简单实现
使用了延时器和闭包,有效解决了执行频率过高的问题,让某个时间期限内只执行一次事件处理函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 简单防抖函数
function debounce(fn, wait) {
var timer = null;
// 闭包
return function () {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(fn, wait);
};
}
function showTop() {
var scrollTop = document.documentElement.scrollTop;
console.log("滚动条位置:" + scrollTop);
}
window.onscroll = debounce(showTop, 300);
节流 Throttle
节流情景
- 图片懒加载
- ajax 数据请求加载
节流原理
如果持续触发事件,每隔一段时间只执行一次函数。
官方解释:当持续触发事件时,保证一定时间段内只调用一次事件处理函数。
“你别一直催!你催得再急我也是干这么快,这点时间我只完成一次。”
节流函数简单实现
- 使用了延时器和闭包,即便一直滚动不松手,也会在指定时间间隔时执行一次函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// 简单节流函数
function throttle(fn, wait) {
var valid = true;
// 闭包
return function () {
if (!valid) {
return false;
}
valid = false;
setTimeout(function () {
fn();
valid = true;
}, wait);
};
}
function showTop() {
var scrollTop = document.documentElement.scrollTop;
console.log("滚动条位置:" + scrollTop);
}
window.onscroll = throttle(showTop, 1000);
实际开发场景
总的来说,在某事件持续触发时,防抖只会等最后一次触发后执行某函数,节流会保证在指定间隔时间段内执行某函数。
- 搜索框input事件,例如要支持输入实时搜索:可以使用节流方案(间隔一段时间就必须查询相关内容);或者使用防抖方案,实现输入间隔大于某个值(如500ms),就当做用户输入完成,然后开始搜索。(具体使用哪种方案要看业务需求)
- 页面resize事件,常见于需要做页面适配的时候,需要根据最终呈现的页面情况进行dom渲染(这种情形一般是使用防抖,因为只需要判断最后一次的变化情况)
总结与代码示例
本部分内容由微软 New Bing 撰写
防抖和节流都是前端性能优化的方法,用于控制函数的执行频率。
- 防抖是指在一定时间内,只有最后一次操作才会触发函数执行,例如搜索框输入时的自动提示2。节流是指在一定时间内,只允许函数执行一次,例如滚动条滚动时的图片懒加载。
- 防抖和节流的目的是为了减少不必要的计算和渲染,提高页面性能和用户体验。
防抖的代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25// 防抖函数
function debounce(fn, delay) {
// 定义一个定时器变量
let timer = null;
// 返回一个新的函数
return function() {
// 清除上一次的定时器
clearTimeout(timer);
// 获取函数的执行上下文和参数
let context = this;
let args = arguments;
// 设置一个新的定时器,延迟执行函数
timer = setTimeout(function() {
fn.apply(context, args);
}, delay);
}
}
// 测试用例:输入框搜索功能
let input = document.getElementById('input');
let ajax = function(value) {
console.log('发送请求,搜索关键词:' + value);
}
// 给输入框绑定防抖后的事件处理函数
input.addEventListener('input', debounce(ajax, 500));节流的代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// 节流函数
function throttle(fn, delay) {
// 定义一个标志位,表示是否可以执行函数
let flag = true;
// 返回一个新的函数
return function() {
// 如果标志位为false,表示不能执行,直接返回
if (!flag) return;
// 如果标志位为true,表示可以执行,先将标志位设为false
flag = false;
// 获取函数的执行上下文和参数
let context = this;
let args = arguments;
// 设置一个定时器,延迟恢复标志位为true,并执行函数
setTimeout(function() {
flag = true;
fn.apply(context, args);
}, delay);
}
}
// 测试用例:滚动条滚动事件监听功能(假设有个叫scrollHandler的处理函数)
window.addEventListener('scroll', throttle(scrollHandler, 300));
【参考内容】:
[1] CSDN - JS 节流与防抖
[2] 哔哩哔哩 - 课程分享