前端性能优化常见方法
背景
由于DOM树的构建和JS代码的执行都要在主进程(渲染进程)上进行,所以当JS代码执行时间过长时(任务执行时长 > 50ms)即出现长任务时会导致页面卡顿掉帧现象
一、优化 JavaScript 执行效率
1、根据任务管理策略拆分长任务
function main(){
task1();
task2();
task3();
task4();
task5();
}
2、手动推迟代码执行
将长任务(执行时间 > 50ms)拆分为小任务,用requestIdleCallback或setTimeout分片执行:
// 长任务拆分示例
const tasks = [/* 大量待处理任务 */];
const processTask = () => {
const start = performance.now();
// 每次处理一部分,确保执行时间不超过20ms
while (tasks.length && performance.now() - start < 20) {
const task = tasks.shift();
process(task); // 处理单个小任务
}
if (tasks.length) {
requestIdleCallback(processTask); // 利用空闲时间继续执行
}
};
requestIdleCallback(processTask);
3、使用调度器API(有兼容问题)
调度器API提供一个postTask()函数,帮助浏览器确认任务优先级
postTask() API有三个优先级:
-
'user-blocking'
最高优先级,用于处理用户当前正在交互的任务,如输入响应、动画更新等,需要立即执行以保证流畅的用户体验。 -
'user-visible'
中等优先级,用于处理用户可见的任务,如页面内容渲染、数据更新等,这些任务不需要立即响应但对用户可见性有影响。 -
'background'
最低优先级,用于处理后台任务,如数据同步、日志记录等,这些任务不紧急且用户通常不会立即感知到。
function main(){
scheduler.postTask(task1, {priority: 'user-blocking'});
scheduler.postTask(task2, {priority: 'user-visible'});
scheduler.postTask(task3, {priority: 'background'});
}
4、减少不必要的 JavaScript
- 移除未使用的代码(利用
tree-shaking、代码分析工具如webpack-bundle-analyzer)。 - 避免加载冗余库,例如仅需日期格式化时,用轻量库
date-fns替代完整的moment.js。 - 拆分大文件,通过代码分割(
React.lazy、dynamic import)实现按需加载。
二、使用 Web Worker 隔离计算密集型任务
Web Worker 允许在后台线程执行脚本,完全不阻塞主线程,适合处理:
- 大数据解析(如 CSV/JSON 处理)、复杂计算(图表渲染、数据可视化)
- 图片 / 视频处理、加密解密等耗时操作
// 主线程
const worker = new Worker('data-processor.js');
// 发送数据给Worker
worker.postMessage(largeDataset);
// 接收处理结果
worker.onmessage = (e) => {
console.log('处理结果:', e.data);
};
// data-processor.js(Worker线程)
self.onmessage = (e) => {
const result = heavyComputation(e.data); // 密集计算
self.postMessage(result); // 发送结果回主线程
};
三、优化 DOM 操作与渲染
DOM 操作是主线程的高开销任务,需减少不必要的渲染压力:
-
减少 DOM 节点数量
- 避免嵌套过深的 DOM 结构(推荐深度≤6 层),减少样式计算和布局成本。
- 用虚拟列表(如
react-window)处理长列表,只渲染可视区域内的元素。
-
批量 DOM 操作
- 避免频繁的 DOM 增删改,使用
DocumentFragment或离线 DOM(如先修改display: none的元素)批量处理: -
const fragment = document.createDocumentFragment(); // 批量添加节点到fragment(不会触发重绘) items.forEach(item => { const div = document.createElement('div'); div.textContent = item; fragment.appendChild(div); }); // 一次性插入DOM container.appendChild(fragment);
- 避免频繁的 DOM 增删改,使用
3.避免强制同步布局
浏览器会批量执行布局计算,若在修改样式后立即读取布局属性(如offsetHeight、getBoundingClientRect),会强制浏览器立即计算布局,导致性能损耗:
// 坏例子:强制同步布局
elements.forEach(el => {
el.style.width = '100px'; // 修改样式
const height = el.offsetHeight; // 立即读取,触发强制布局
});
// 好例子:先读取,后修改
const heights = elements.map(el => el.offsetHeight); // 批量读取
elements.forEach((el, i) => {
el.style.width = `${heights[i]}px`; // 批量修改
});
4.优化样式与动画
- 优先使用
transform和opacity实现动画(由 compositor 线程处理,不阻塞主线程)。 - 避免使用
box-shadow、filter等属性(会触发频繁重绘)。
四、优化资源加载与解析
-
延迟加载非关键资源
- 对非首屏图片、视频使用
loading="lazy"属性延迟加载。 - 异步加载第三方脚本(如广告、社交插件),添加
async或defer属性:<script src="third-party.js" async></script> <!-- 下载完成后立即执行(顺序不定) --> <script src="analytics.js" defer></script> <!-- 文档解析完成后按顺序执行 -->
- 对非首屏图片、视频使用
-
预编译与缓存
- 利用
link rel="preload"预加载关键资源(如字体、核心 JS),提前加载但不立即执行。 - 合理设置 HTTP 缓存(
Cache-Control),减少重复下载和解析。
- 利用
-
减少解析阻塞
- 将非关键 CSS 放到
<link rel="stylesheet" media="print" onload="this.media='all'">,延迟非首屏样式解析。 - 内联首屏必要 CSS(Critical CSS),避免外部 CSS 阻塞渲染。
- 将非关键 CSS 放到
更多推荐
所有评论(0)