如何使用Chrome Performance优化性能问题
本次来分享下ChromeDevTools Performance面板的使用技巧,对于前端开发者来说页面的性能表现在运行指标中占比是很高的。面对复杂的项目业务难免会有在代码层面造成的性能问题,对于浏览器端开发者来说掌握Performance相关技巧还是非常重要的
小贴士
文章中涉及到的示例代码你都可以从 这里查看 ,若对你有用还望点赞支持
打开面板
首先要打开Performance面板我们才可以进行相关操作,默认情况下打开开发者工具后,在顶部的tabs中就会看到有个Performance选项,直接点击就可以,或者点击最后面的更多按钮查看更多
如果没有可以打开面板然后输入Command+Shift+P
调出命令面板后,再输入Performance
看到对应的条例后点击它就可以打开了
这是一份完整的面板结果示例
Performance面板由 控制面板
、概览面板
、火焰图
和 详情面板
几部分组成
控制面板
控制面板位于顶部,主要用于管理性能分析的录制过程和配置选项。以下是控制面板的主要功能介绍:
Record 按钮
:开始或停止性能数据的录制,结束后会生成一份详细的性能分析报告Reload 按钮
:在录制期间重新加载页面,通常用于分析页面加载过程的性能表现(如首次绘制、资源加载)Clear 按钮
:清除当前录制的数据ScreenShots
:用于控制记录期间是否记录屏幕截图,一般用它来观察动画的具体过程是否流程等等Memory
:用于控制记录期间是否开启内存大小变化,开启后就可以查看内存的使用情况了
除此之外你还可以点击最后面的设置按钮查看更多的功能选项:
比如:控制CPU的执行效率、网络的网速、样式统计等等,这些功能给开发者模拟不同的环境提供了很多便利
开始记录
开始一份性能记录可以通过 重载页面开始记录
或 运行时记录
,二者的区别在于前者可以记录页面的加载过程,而运行时记录则就是从当前页面的状态开始记录,根据用户的操作等等产生一份报告
读者可以根据实际情况选择记录
概览面板
概览面板用于展示性能数据的整体概览,开发者可以通过概览面板快速识别性能瓶颈和页面的关键问题点。概览面板也是横轴为时间轴,纵轴是不同维度的记录情况。在上图可以看到:
CPU
:有颜色的区域标识CPU在工作Net
:网络活动图展示页面的网络请求活动,包括资源的加载时间和数据量。蓝色代表资源加载, 黄色代表请求延迟- 截图:截图可以看到每一帧的网页展示情况
Heap
:内存活动图记录内存随时间的推移使用情况
通过鼠标滚轮可以放大或缩小时间轴,方便开发者可以更专注于某一个时间点的排查
火焰图
火焰图(Flame Chart)是 Chrome DevTools Performance 面板中的一个核心部分,用于展示和分析页面性能数据的详细信息。它以火焰状的层次结构显示各个任务的执行顺序和耗时,帮助开发者深入了解页面的主线程和其他线程的性能瓶颈
火焰图的顶部是时间轴,单位为毫秒(ms),表示录制过程中各个任务发生的时间点。时间轴从左到右依次递增,反映任务的执行顺序
火焰图的纵轴是以不同维度来记录的性能情况,如:Network
、Frame
、Main主线程
、GPU
、其他线程
等等
通常情况下的性能问题我们都是会从主线程开始,他记录了页面的加载解析、绘制、js的执行等等情况,而这些通常都是和开发者息息相关的问题
任务
分析主线程首先要知道它的执行机制是怎么回事❓
主线程以任务为基本单位不断执行,这些任务覆盖很广,包括:页面解析、渲染、JS执行、事件等等。主线程每次只能运行一个任务,所有的任务按循序执行,下图是一个主线程的任务执行图:
每个任务包含一系列的调用过程,通常被称为调用栈
不同的任务会被标记为不同的颜色,黄色js执行任务
、紫色样式计算布局等
、绿色渲染任务如绘制和合成
、蓝色网络请求
优化长任务
当任务的执行时间超过 50-60ms 就会被认为是长任务,长任务被看作是阻碍性能的头号大敌。火焰图上也会通过右上角的红色标识
这是长任务,通过这个可以快速的看到那些任务有性能问题
初次之外一些会造成页面卡顿、帧率问题的任务通常会被显示为红色
当知道了哪些任务出现问题时,就可以聚焦这里开始分析了。通常火焰图需要结合详情信息区域进行排查具体的代码问题
详细信息
详细信息区域位于性能分析的底部,它用于显示当前选中任务的详细性能数据和相关信息,帮助开发者深入理解性能瓶颈的具体原因
详细信息由Summary(概要)
、Bottom-Up(自下而上分析)
、Call Tree(调用栈)
、Event Log(事件日志)
几部分组成
Summary
提供选中任务的简要信息,将当前选中任务
的所有任务类型执行情况以饼图形式展示出来
上面包含了加载时间、脚本执行时间、页面渲染时间、绘制时间、Idle空闲时间等等,通过饼图面板可以快速的看到什么类型的任务最耗时
Bottom-Up
按照任务的耗时进行聚合,展示各个任务的调用情况以及记录执行的耗时情况,读者可以点击不同的聚合直到底层执行
每个聚合包含当前函数调用时间以及子调用的执行时间
这里也可以看出哪些函数任务执行很耗时
Call Tree
显示选中任务的调用堆栈,包含函数调用链的每一层、文件名、行号和列号,顶层为当前任务,底层为最初的调用者。通过调用栈可以很方便的看到一些长任务很耗时的脚本执行问题所在
找对应的长任务后点击后方的文件名就可以跳转到源码位置(页面加载的文件)
Chrome会在源码左侧位置标注当前代码的执行时间
EventLog
列出选中时间范围内发生的所有事件,包括事件的名称、时间戳和持续时间
通过此种方式可以快速看到有哪些事件非常耗时
实时帧率
除了通过Performance记录性能外还有很多功能可以使用,如实时帧率用来实时监测页面的指标。开发者通过在命令面板输入:fps
点击第一项就可以了
打开后页面上会多一个实时面板,包含帧率、GPU、内存等指标
实时性能监控
开发者通过在命令面板输入:performance
然后选择monitor
打开后会有一个监控窗口出现
其他
Chrome DevTools还有很多强大的功能,如:断点、内存分析、Lighthouse、Layer等等,这些你都可以在命令面板中找到 Command+Shif+P
,记得尝试一下
实战案例
这里我们通过一个项目来演练下如何优化性能,项目源码
页面代码:
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="./style.css">
<script src="./long-task.js"></script>
</head>
<body>
<div class="box"></div>
<script>
const box = document.querySelector('.box');
box.addEventListener('click', doWhile);
async function doWhile() { for (let i = 0; i < 10e8; i++) {}}
</script>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
long-task.js
源码:
for (let i = 0; i < 10e7; i++) {}
然后通过Chrome打开页面,开始Performance记录加载性能
通过性能结果报告可以一眼看出来哪里有性能瓶颈问题,我们通过红区就可以一眼看出
优化长任务
选中这里的任务查看详情面板
发现这里的doWhile
函数执行太耗时了,我们点击到对应的源码位置
这里的业务逻辑紧紧是演示,那如果真实的情况也会发生这种执行耗时的任务呢?我们该怎么办❓
大家知道JS的EventLoop机制,而主线程上的任务就是EventLoop不断执行的过程,如果任务太耗时就会造成页面卡顿。那么通过EventLoop我们可以将 长任务拆分为多个小任务通过EventLoop不断执行,这样就不会造成卡顿了
我们改造下代码:通过Promise
将任务不断推到下一个eventloop
async function doWhile() {
for (let i = 0; i < 10e8; i++) {
console.log("click...");
await new Promise(resolve => {
setTimeout(resolve, 0);
});
}
}
2
3
4
5
6
7
8
再来看看效果:
效果前后差异太大了,根本看不到一点卡顿‼️
除了Promise外,还可以通过主动使用调度器来避免卡顿问题,这里读者要知道卡顿是因为这段js任务一直占据着主线程,导致后续任务无法及时影响!那么开发者在写这种耗时任务时通过调度器将主线程让出,及时响应其他任务就会有卡顿了
小贴士
scheduler API比较新部分浏览器还不支持,因此读者可以选择polyfill或者注意使用浏览器的版本
这里使用 scheduler API 进行调度:
async function doWhile() {
for (let i = 0; i < 10e8; i++) {
console.log("click...");
await scheduler.yield();
}
}
2
3
4
5
6
来看看效果:
会发现使用了调度器后还是会有一点点卡顿,但是不是很明显的卡顿,这里有个调度器的使用说明
scheduler.yield()
快速且高效,但会产生一些额外开销。如果 jobQueue 中的某些作业非常短,那么开销可能会迅速累积,导致用于让出和恢复的时间比执行实际工作的时间还多
优化资源加载
在解决了点击事件的问题后,我们用鼠标放大火焰图拉到最前面,就会看到这里也有一个很耗时的任务
根据任务的执行栈可以看出在解析HTML的过程中去执行了JS脚本导致任务过长,页面中确实有一个加载js脚本资源的代码。从图中最后面可以看到有一个蓝色线旁边写着 DCL
即Document Content Load也就是页面资源加载完毕,然后就会触发DomContentLoad
事件,而这段js在这事件之前就执行了,这会严重影响页面指标
一般对于非首页很重的资源采取异步加载,然后根据执行时机选择defer
和async
,这里我们选择defer
<script defer src="./long-task.js"></script>
再次查看执行情况:
发现当前脚本任务已经没有阻塞HTML解析了,而且任务的执行会超过DCL
,会爆红根本原因在于太耗时。所以在项目中要对不是很重要的js资源做异步加载
优化重绘/回流
现在我们通过鼠标滚轮将视角拉到全局,会发现密密麻麻的一片紫色,占据了整个时间轴,紫色代表着页面布局渲染。由于页面上的box
盒子一直在没有优化的情况下不断运动,所以会造成页面渲染
我们随便选一个任务,然后从详情记录面板查看具体情况,发现会执行:布局
、样式计算
、重绘
、分层
等多个步骤
来看下我们的动画代码:
.box {
width: 100px;
height: 100px;
background-color: red;
margin: 10px;
animation: 1s linear infinite alternate box-animation;
}
@keyframes box-animation {
from { margin-left: 0; }
to { margin-left: 200px; }
}
2
3
4
5
6
7
8
9
10
11
动画代码通过改变margin
的形式改变位置,由于改变了元素的几何,因此会造成页面的重绘,这种动画是非常消耗性能的
现在我们优化下动画:
.box.optimized {
will-change: transform;
animation-name: box-animation-optimized;
}
@keyframes box-animation-optimized {
from { transform: translateX(0); }
to { transform: translateX(200px); }
}
2
3
4
5
6
7
8
上面我们使用will-change
告诉浏览器这个元素要分层处理
,然后动画改成transform
合成动画,合成动画只会在合成线程
上执行,而不会触发主线程进行重新渲染页面,大大提高效率
关于这些优化如果你不是很清楚的话,推荐阅读往期文章 「现代浏览器架构」
再来看看优化后的效果:
前后对比太明显了,后续的任务根本没有显示任何执行任务,因为它根本没在主线程上运行
到这里的实战就告一段落了,还有很多实用的技能,敬请关注我后续文章
总结
通过本文让读者可以了解到主线程是以任务为基本单位执行的,合理管理每个任务的执行时长以及样式动画,对提高页面运行效率有重要作用。这里简单归纳下:
- 避免阻塞主线程
- 拆分长任务
- 禁止强制布局
- 先赋值再布局