性能优化
1、 回流和重绘(Repaint & Reflow)
回流 是指浏览器重新计算网页元素的布局和位置的过程。当页面元素的尺寸、位置或显示状态发生变化时,就会触发回流。例如,当您添加或删除元素、更改元素的样式或大小、或改变窗口大小时,都会触发回流。
重绘 是指浏览器重新绘制网页元素的外观的过程。当元素的样式(例如颜色、边框或背景)发生变化时,就会触发重绘。即使元素的布局和位置没有变化,也会触发重绘。
1.1、 回流触发时机
- 添加或删除可见的DOM元素
- 元素的位置发生变化
- 元素的尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)
- 内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代
- 页面一开始渲染的时候(这避免不了)
- 浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小的)
1.2、 重绘触发时机
- 颜色的修改
- 文本方向的修改
- 阴影的修改
1.3、 如何最小化重绘和回流
- 需要要对元素进行复杂的操作时,可以先隐藏(display:"none"),操作完成后再显示
- 需要创建多个DOM节点时,使用DocumentFragment创建完后一次性的加入document
- 缓存Layout属性值,如:var left = elem.offsetLeft; 这样,多次使用 left 只产生一次回流
- 尽量避免用table布局(table元素一旦触发回流就会导致table里所有的其它元素回流)
- 避免使用css表达式(expression),因为每次调用都会重新计算值(包括加载页面)
- 尽量使用 css 属性简写,如:用 border 代替 border-width, border-style, border-color
- 批量修改元素样式:elem.className 和 elem.style.cssText 代替 elem.style.xxx
- 避免设置多项内联样式
- 应用元素的动画,使用 position 属性的 fixed 值或 absolute 值(如前文示例所提)
- 避免使用 table 布局,table 中每个元素的大小以及内容的改动,都会导致整个 table 的重新计算
- 对于那些复杂的动画,对其设置 position: fixed/absolute,尽可能地使元素脱离文档流,从而减少对其他元素的影响
- 使用css3硬件加速,可以让transform、opacity、filters这些动画不会引起回流重绘
- 避免使用 CSS 的 JavaScript 表达式
- 如果想设定元素的样式,通过改变元素的 class 类名 (尽可能在 DOM 树的最里层)
2、 防抖和节流
前端的防抖和节流都是用来控制函数执行频率的技术,它们的区别在于:
- 防抖(debounce):在一段时间内只执行一次函数。如果在等待时间内又触发了该事件,那么会重新计时等待。防抖适用于需要等待用户停止操作后再执行的场景,例如输入框输入完成后进行搜索。
- 节流(throttle):在一段时间内只执行一次函数。无论触发事件的频率有多高,都会按照固定的时间间隔执行函数。节流适用于需要控制函数执行频率的场景,例如滚动事件处理、鼠标移动事件、页面滚动加载、限制请求频率等。
防抖的最经典场景是:
- 输入框搜索:用户在输入框中输入时,会频繁触发输入事件。如果每次输入都触发搜索请求,就会造成大量的网络请求,影响性能。使用防抖可以限制搜索请求的频率,例如在用户停止输入 500ms 之后再触发搜索请求。
节流的最经典场景是:
- 窗口滚动:用户滚动窗口时,会频繁触发滚动事件。如果每次滚动都触发页面重新渲染,就会造成卡顿。使用节流可以限制页面重新渲染的频率,例如每隔 100ms 触发一次页面重新渲染。
代码实现
节流
function debounce(fn, delay) {
let timer = null;
return function(event) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn(event);
timer = null;
}, delay);
};
}
// 使用防抖函数
const input = document.getElementById('input');
input.addEventListener('input', debounce(handleInput, 500));
function handleInput(event) {
// 处理输入
}
防抖
function throttle(fn, delay) {
let lastCallTime = 0;
return function(event) {
const now = Date.now();
const delta = now - lastCallTime;
if (delta >= delay) {
fn(event);
lastCallTime = now;
}
};
}
// 使用节流函数
const window = document.getElementById('window');
window.addEventListener('scroll', throttle(handleScroll, 100));
function handleScroll(event) {
// 处理滚动
}