写点什么

开发者必看!前端性能调优工具 Performance 面板实战

作者:OpenTiny社区
  • 2025-08-15
    广东
  • 本文字数:6194 字

    阅读完需:约 20 分钟

开发者必看!前端性能调优工具Performance面板实战

本文由体验技术团队董福俊原创。

一、背景

关于 Performance 面板的基础用法介绍,可参考上一篇文章《"Performance 面板" 一文通,解锁前端性能优化工具基础用法!》。文章中还从一个 HTTP 请求的四阶段的角度来介绍 Performance 图的 "观看方式",并重点介绍了 worker 线程跟主线程的协作关系

本篇文章中,我们将会以一个实际网页 ------VPC 列表页为例,介绍 Performance 抓图及分析的过程,并将上一篇文章中介绍的相关内容串起来,希望每位 frontend developer 都能掌握用 Performance 分析页面性能的能力。

PS:这里假设我们的分析目标是:让列表页的主内容区域尽快展示出来,以避免长时间白屏;另外,为求简洁,下文叙述中统一将 Performance 录制的结果图叫做 "性能图"

二、分析前准备

正式分析之前,建议做一些准备工作,能有效提高后续的分析效率。

1. 环境准备

1)选一个良辰吉日

建议关闭一些应用程序、浏览器页签,尽量让电脑 CPU 空闲一些 ,(也建议关掉通信软件,让自己心情好一些)。尽量避免一边开会一边分析,避免分析过程被频繁打断,因为分析过程可能遇到各种奇怪的现象,倘若再被其它事务打断,心态直接崩溃,那么就分析不下去了......

2)选浏览器无痕模式

建议在浏览器无痕模式下录制性能图并进行分析,因为一些特殊的浏览器配置、浏览器插件 都可能影响网页的加载过程,干扰分析结果。

3)选生产环境进行分析

我们可能有多种不同环境:本地代理环境(俗称 localhost)、类生产环境、生产环境 等。只有生产环境是最真实贴近用户的,所以建议在生产环境下进行网页性能分析。

但需要注意的是,生产环境部署的代码,跟源码往往有较大形态上的差异(eg:打包工程的混淆、部署时的加工、服务端渲染的处理等)。所以,生产环境的性能图在某些代码细节上可能无法深入研究,此刻,我们还是需要其它环境的性能图进行辅助对比。

2. 代码准备

1)了解代码结构和加载流程

建议先从源码角度,详细了解待分析页面的结构及加载流程,例如我们讨论的样例 ------ VPC 列表页的结构如下:

页面结构



  • 红色框:由基础框架 console-ui 提供的 header、sidebar 实现

  • 蓝色框:由业务代码实现,采用 angular 技术框架,NG App 下挂载一个根组件(VpcComponent)

  • 绿色框:根组件下分为 导航组件(LeftmenuComponent)和 列表页组件(VpcListComponent)

代码结构

<!-- index.html -->
<div id="header"><!-- console-ui负责渲染 --></div>
<div id="sidebar"><!-- console-ui负责渲染 --></div>
<!-- NG App 挂载点 -->
<div id="ngApp">
<!-- 根组件 -->
<vpc-component>
<leftmenu-component><!-- 导航组件 --></leftmenu-component>
<router-outlet>
<!-- 内容区-路由渲染点 -->
<vpc-list-component><!-- 列表页组件 --></vpc-list-component>
</router-outlet>
</vpc-component>
</div>
复制代码
  • console-ui 和 NG App 并行工作,分别负责不同 div 的渲染

  • APP 的根组件下,导航组件和内容区并行工作,内容区由路由加载列表组件

初始化流程



  • 主渲染流程是:NG App 启动 => 加载根组件 => 路由渲染 => 加载列表页组件

  • angular 中每个组件都会经历生命周期:constructor => ... => ngOnInit => ... => ngAfterViewInit => ... (这里只列举一些常用生命周期钩子,完整的说明见 组件生命周期PS:这一点不理解也不妨碍下文阅读

2)关键节点加 performance.mark

很关键的是:在我们的主渲染流程上一些关键的时间节点加上 performance.mark,或者关键时间段加上 console.time/timeEnd。这样在性能图的 Timings 面板上就会显示这些标记,从而帮助我们确认各个执行环节。

例如,针对 VPC 列表页的主渲染流程,我们在 NG App 启动、根组件 constructor 及 ngAfterViewInit、列表页组件 constructor 及 ngAfterViewInit,分别加上 performance.mark,则能在 Timings 面板中看到下图情况:



PS1:Timings 面板中的线很细,不仔细观察可能会漏掉。

PS2:为了针对生产环境进行分析,我们可能需要提前在代码中预埋 performance.mark,待上线后才能利用的上。

3)录制性能图

在 Network 面板清空已有记录,在 Performance 面板多点几下垃圾回收,然后开始录制。录制完成后,将 Performance 面板的性能图导出成 json 文件,并将 Network 面板的网路请求记录导出成 har 文件,保存在本地以方便后续查看。

三、数据分析

录制好性能图后,建议先分析 Network 面板中的 index.html,大致了解加载了哪些静态资源,然后再投入性能图的分析

1. 分析 index.html

首先分析下 Network 面板中的 index.html,大致了解页面加载了哪些静态资源。 一般源码中的 index.html 跟浏览器实际执行的 index.html 往往有很大差别,因为代码 (打包) 工程会对 index.html 进行魔改,如果有服务端渲染机制,那么服务渲染时可能还会插入一些样式、脚本。所以,浏览器最终执行的 index.html 将会是 源码 + 工程修改 + 服务端渲染 之后的结果。

例如,针对 VPC 列表页的 html 进行分析,会发现其加载了以下资源(按 html 中从上到下的顺序排列)



我们需要搞清楚浏览器会开几个 TCP 连接 ?哪些资源会挤在同一个连接中?因为同一个连接中的资源会互相争抢网络带宽 

分辨方法是:h2 下同一个域名的资源会共用同一个 TCP 连接,http/1.1 下同一个域名可能会开多个 TCP 连接,资源们按顺序排队。 所以上表中,所有 CDN 域下的资源共用同一个 TCP 连接,Server 域下只有一个资源,暂时只开一个 TCP 连接。

PS:说 "暂时" 是因为,后续可能会有动态加载 js、或者发起 ajax、fetch 请求,可能会增加 TCP 连接数量

另外,下载优先级是由浏览器综合分析并自动分配的,我们无法直接指定优先级。并且不同版本浏览器的分配策略各异,但大多数情况下,会遵循如下规则:

  • 通过 <script> 标签加载的 js 脚本的优先级高于动态创建 script 的优先级。(动态创建例如:通过 appendChild 往 DOM 中插入一个 <script> 标签)

  • <script> 标签上没有加任何标记(module、async、defer)的,优先级最高

  • <script> 标签被标记了 type="module" 的,优先级较高

  • <script> 标签被标记了 async、defer 的,优先级较低

值得说明的是,项目采用了 webpack 打包,其中 main.{hash}.js 就是 webpack 主入口文件,而 vendor~tinycloud 等文件均是分包策略拆出来的 chunks 们。

2. 分析 Performance 数据

接着,我们就要分析性能图了,我们的首要目标是:搞清楚性能图中各个环节在做什么,并将它跟初始化流程一一对应起来

2.1 分析映射关系:代码 <=> 性能图



首先观察性能图中 Network(网络情况)、Main(CPU 情况)、Timings(我们预埋的 mark 标记) 这三个部分,尝试搞清楚图中每个时间段里面,浏览器在忙些什么。 以上图为例,大致流程是:

  • 时段 1:网络繁忙、CPU 空闲

  • 时段 2:网络空闲、CPU 繁忙

  • 时段 3:网络 CPU 都繁忙

  • NG App 启动从时段 3 才开始

接下就是详细分析过程:

1)时段 1:静态资源下载

分析每一个请求,搞清楚它是谁发起的,在业务上有什么作用。 点开一个资源条,就可以在 Summary 面板中看到它的一些基础信息。

PS:有时候,Summary 面板中写的 Initiated by 不一定是真实的发起者,真实发起者可能被层层代码封装隐藏了,我们需要到 Network 面板中的 Initiator 里面去找真实发起者



对时段 1 的所有请求进行分析之后,我们就搞清楚了这个阶段的具体情况,如下:



  • 绿色部分由 console-ui 发起

    首轮请求 (html 中通过 script 直接引用):仅 consoleui.umd.js 这 1 个资源 (CDN 域 /h2)

    非首轮请求 (由某些逻辑动态发起):5 个静态资源 (CDN 域 /h2) ;若干 Fetch/XHR 请求 (Server 域 /http1.1)

  • 红色部分由业务代码发起

    首轮请求 (html 中通过 script 直接引用):runtime、polyfill、......、main 等 11 个资源 (CDN 域 /h2)

    非首轮请求 (由某些逻辑动态发起):无

  • 蓝色部分由 cc 组件(一个三方业务组件,负责一些特殊业务组件的实现)发起

    首轮请求 (html 中通过 script 直接引用):无

    非首轮请求 (由某些逻辑动态发起):cc-main.js (Server 域 /http1.1) 及若干 main、theme 等 (CDN 域 /h2)

时段小结: 这一时段主要是完成各种 HTTP 请求,主要有 业务代码、console-ui、cc 组件三方参与。首轮请求主要是业务代码的请求,采用 h2 协议,console-ui 及 cc 组件则多为非首轮请求。

这里其实可以看出来,业务代码发起的静态资源请求几乎独占首轮请求,其它请求均是在后续轮发起,不会影响业务静态资源请求的完成。并且所有 Fetch/XHR 请求都是使用 Server 域 /http1.1,跟静态资源使用的 CDN 域 /h2 是两个不同的 TCP 通道,不会影响业务静态资源的加载。

2)时段 2:webpack 代码展开

分析火焰图中每个色块,搞清楚它们是属于哪个代码文件的内容,它们在执行什么?



上一篇文章中介绍过,每个色块是一个函数,色块的名字是函数名,色块上下关系是函数调用关系,这一整个火焰图就是调用栈的直观展示。

  • 查看色块归属:在上一篇文章中提到过,webpack 打包时会将 js 代码用匿名函数包裹,并指定一个数字 key,所以就产生了这些数字命名的函数。点击色块可以看到它属于哪个代码文件。

  • 查看色块在做什么:查看这个 task 的火焰图的栈底,会发现全是黄色的 Compile code 块,说明在编译代码(V8 引擎的惰性编译策略)

  • 整个展开动作的起点:等待初始 chunk 全都下载完之后,才开始代码展开。

时段小结:这一时段主要在做 webpack 的代码展开,CPU 在忙着编译、执行被 webpack 打包的代码

这里需要对 webpack 打包产物有一定了解,才能透彻了解这个过程,简要说明如下:



标题中所谓的 webpack 代码展开,其实就是执行这些数字命名的包裹函数,而 V8 引擎的惰性编译策略,可能不会在流式下载文件的环节就直接编译这些代码,所以栈底全都是 compile code 的色块。另外,webpack 的启动机制会保证所有 chunk 下载完成后,才启动代码展开工作,从 时段 1 => 时段 2 的衔接点可以看到,虽然主 chunk 文件 main.xxxx.js 早已下载完成,但代码没有立即展开,而是等待最后一个 chunk 下载完成之后,才进行展开。

3)时段 3:动态下载语言包等主题资源



利用时段 1 提到的分析方法,对时段 3 的请求也做同样的分析可知:

  • 主要是语言包、主题资源包、docs 等通用资源的下载,主要使用 CDN 域 /h2 的通道。

  • 均是由业务代码发起。

利用时段 2 提到的分析方法,对时段 3 的火焰图的各个色块也进行同样的分析可知:

  • 一些由微任务或定时器,唤起的 console-ui 逻辑被执行

  • 一些 cc 组件逻辑被执行

  • 还存在着若干空闲 task 时间

同时,从 Timings 面板可以看出,在 webpack 代码展开之后、时段 3 开始之前,NG App 就已经 boot 了,但是在时段 3 结束后,根组件才开始 construct。

为什么 angular 的 app 已经开始 boot 了,但根组件没有尽快 construct?并且这中间还有空闲的 CPU 时间。 这里需要结合代码实现来分析:

  • 该页面支持多语言,为了不一窝蜂的下载所有语种的资源包,代码中采用按需下载的模式:仅在确认了当前语种之后,才开始下载该语种的资源包。

  • 代码中利用 angular router 的路由守卫,在守卫中异步下载当前语种对应的资源包。angular router 会确保根组件在守卫完成后再开始 construct。

所以,问题的答案是:根组件在等待资源包的下载完成。从图中也可以看出,根组件 construct 是在 docs 接口完成之后的第 1 个 task 中进行的。

时段小结: 这一时段主要是下载当前语种对应的资源包,而这些资源包是根组件 construct 的前提条件(因为路由守卫的原因) 。

而在资源下载期间,CPU 被用于执行一些 console-ui、cc 组件等非业务逻辑,或者直接空闲。因为在这期间,也没有业务逻辑代码可供执行了。

4)时段 4:关于时段 3 之后

从 Timings 面板中可知,在时段 3 之后、特别是根组件 construct 之后,根组件 afterViewInit、列表组件的 construct 和 afterViewInit 都紧锣密鼓的执行起来了。这一段 CPU 极其繁忙,按生命周期顺序执行各个组件的初始化,直到列表组件的 afterViewInit 完成后,列表页主内容区域才展示出来。

针对这种任务密集时段,我们当然也可以用时段 2 中提到的分析方法,针对火焰图中各个色块进行分析。但从生产环境录制的性能图中看到的色块名(函数名)可能都是混淆之后的结果,不利于跟源码对应起来。此时,我们可以针对本地调试环境(localhost)录制性能图并进行分析,因为代码执行在火焰图上的表现,往往不会受到环境的影响。例如该时段中有这么一段:



可以看出有一段结构很相似的调用,出现了多次重复。从 localhost 环境抓取的性能图中就可以明显看出,这段重复执行的是 detectChangesInEmbeddedViews 函数,它是 angular 的内部函数。在嵌入式视图场景中,如果有大量 ngFor、ngIf 就会触发。通过源码分析,这一段正是导航组件中的代码实现造成的。

2.2 在性能图中找问题

通过上一节中对性能图透彻分析之后,我们已经能将性能图跟源码对应起来,并且能将性能图跟加载流程对应起来,同时对一些关键节点心中有数。接下来要做的就是找问题、找可优化的空间,以达到我们的目标。例如针对 VPC 列表页的分析可知,主渲染流程在性能图中大致是:



为了实现目标,我们至少可以找到以下优化点:

  • 下载静态资源能否更快?

    非重要模块,改为懒加载(按需动态 import),减少初始 chunk 的体积

    较大的图片资源等,避免打包成 base64 字符串,减少 chunk 体积

    合理拆分 chunk 包,避免有一个独大的 chunk,充分利用 h2 的并行下载效果

  • webpack 代码展开能否更快?

    减少初始 chunk 的体积。需要展开的代码少了,展开的自然更快

    避免在代码中执行长耗时运算等

  • 下载语言包资源能否更快?

    语言包资源提前下载,和前面的静态资源一起下载。

  • 组件生命周期能否执行的更快?

    削减高频重复执行的 detectChangesInEmbeddedViews 函数的执行次数。

    减少非必要的渲染内容。

总体上看,不同的项目会有不同的性能图和性能瓶颈点,通用的优化方案可以解决一些常规问题,但当所有常规方案做完之后效果还是不够满意时,我们可能得进行针对性的分析来查找问题。通过深入性能图分析,搞清楚 代码 <=> 性能图 之间的映射关系之后,我们就能轻松找到性能瓶颈点,从而找对应的解决方案了。

四、总结

通过 Performance 面板录制页面加载性能图并进行性能分析,是每一个 frontend developer 进阶的必备技能之一。性能图分析除了要求我们掌握 Performance 面板的基本用法之外,还要求我们对前端相关知识例如:webpack 工程打包、浏览器的加载运行、HTTP 协议机制、前端框架的原理、等都有一定了解,同时要求我们对项目代码的结构和执行流程足够清晰明确。常规的优化方案往往只能解决一些初级、普遍的问题,但每个页面有每个页面的具体情况,只有对页面进行充分分析之后,才能搞清楚页面的性能优化点在哪里,从而有条不紊的落地实施。

关于 OpenTiny

欢迎加入 OpenTiny 开源社区。添加微信小助手:opentiny-official 一起参与交流前端技术~

OpenTiny 官网https://opentiny.design

OpenTiny 代码仓库https://github.com/opentiny

TinyVue 源码https://github.com/opentiny/tiny-vue

TinyEngine 源码: https://github.com/opentiny/tiny-engine

欢迎进入代码仓库 Star🌟TinyEngine、TinyVue、TinyNG、TinyCLI、TinyEditor~ 如果你也想要共建,可以进入代码仓库,找到 good first issue 标签,一起参与开源贡献~

用户头像

OpenTiny 企业级web前端开发解决方案 2023-06-06 加入

官网:opentiny.design 我们是华为云的 OpenTiny 开源社区,会定期为大家分享一些团队内部成员的技术文章或华为云社区优质博文,涉及领域主要涵盖了前端、后台的技术等。

评论

发布
暂无评论
开发者必看!前端性能调优工具Performance面板实战_性能优化_OpenTiny社区_InfoQ写作社区