使用 Performance 看看浏览器在做些什么

图片图片

由下图我们可以看到性能面板呈现的几个主要线程。性能面板并不包含架构中全部的线程,主要还是与页面渲染过程相关的部分。

图片

Network

Network 代表浏览器进程中的网络线程,我们可以看到时间轴上包含了所有的网络请求和文件下载的调用信息,并以不同颜色标识不同类型的资源。

图片

Main

Main 代表渲染进程中的主线程,渲染相关的事情基本都是它来做,脚本执行、样式计算、布局计算、绘制等等。

图片

Compositor & Raster

Compositor 代表渲染进程中的合成线程,Raster 代表渲染进程中的栅格线程。如今浏览器绘制一个页面,可以分为以下几步:

  • 主线程将页面分成若干图层(后文中会提及 Update Layer Tree)

  • 栅格线程分别对每一个层进行栅格化处理

  • 合成线程将栅格化的图块合并成一个页面

图片

我们可以看到,在性能面板中主线程在最后调用了栅格线程做实际的渲染。图片

GPU

显然,这部分就是 GPU Process 中的 GPU 线程。

浏览器的工作报告


接下来我们将大致从时间维度,看看浏览器录制下来的「工作报告」。

文档的下载解析

我们旅途的起点将从点击 Chrome Performance Panel 的 Reload 按钮(形如刷新)开始。当前页面首先进行卸载,伴随着几个日志上报,浏览器开始了 index.html 的下载工作。

图片

HTML 文档下载完成后,浏览器开始按照 HTML 标准对 index.html 进行解析,在主线程中将接收到的文本字符串解析为 DOM 。我们可以注意到,HTML 的解析过程并不是一气呵成,这是因为 HTML 通常还包括了其他外部资源,如图片、CSS、JS 等。这些文件需要通过网络请求或缓存来获取。其中,当 HTML 解析器解析到 <script> 标签时,HTML 文档的解析过程就会中止,转而去加载、解析和执行脚本。因此,从主线程的时间轴可以看出,Parse HTML 的过程是断断续续的。

图片

不同资源的处理

以下处理策略都可以在主线程中看到,但是不同资源的处理条长短差距较大,截图困难,这里不做呈现。

那么浏览器对不同资源的处理策略是怎样的呢?

  • 浏览器下载 HTML 并解析,如果遇到外部 CSS 等资源,就会由 Browser 进程中的 network 线程下载

  • 当 CSS 下载时,HTML 的解析过程可以继续

  • 当解析遇到了外部 Script 标签(不包含 async、defer 属性)时,解析停止,直到脚本下载并执行完成

总的来说,浏览器对 HTML 的解析过程不会被 CSS、IMG 等资源的下载阻塞,但脚本的加载和执行会终止 HTML 的解析。这主要是因为 JS 可能会改变 DOM 的结构,或者是 JS 动态加载其他 JS 再改变 DOM 等潜在问题。

显然,尽管浏览器可以并发几个 network 线程下载资源,但如果仅像上述策略这样处理,当解析到 <script> 时,如果文件较大或者延迟较高,可能会发生「脚本独占线程而没有其他资源在下载」的空窗期(idle network)。因此,pre-loader (或者 preload scanner 等叫法)将会在主线程之外,扫描余下的标签,充分利用 network 线程下载其他资源。这种机制可以优化 19% 的加载时长。

脚本的解析执行

对于重业务逻辑的复杂中后台应用而言,脚本带来的性能开销,往往是占主要地位的。我们从下图的例子就可以看出,去除 beforeunload 之前的卸载,脚本本身的时间开销占比已过半。解析 HTML 在其次,至于其他样式计算、微任务、垃圾回收等等,倒不是最痛的地方。当然,该例子工程本身重业务逻辑,JavaScript 代码量决定着其高成本。

图片

有时我们可以考虑使用 async 或者 defer 属性来提高页面性能,二者的差异不再赘述。需要专门说明的是动态添加脚本的情况。如下面示例代码所示,脚本被 append 到文档中后就会开始下载,并且默认和 async 具有一样的行为,即「先加载完的先执行」。


let script = document.createElement('script');

script.src = "/xxx/a.js";

document.body.append(script);



如果专门设置了 async 属性,则会按照 defer 的行为来,即「先加载到的先执行」。


function loadScript(src) {

  let script = document.createElement('script');

  script.src = src;

  script.async = false;

  document.body.append(script);

}



// 因为 async = false,所以按顺序先执行 big;否则(一般会先)执行 small

loadScript("/xxx/big.js");

loadScript("/xxx/small.js");



从下图中可以看到,调用栈中执行的 appendChild 方法动态添加了 script 脚本,之后很快开始了下载动作。动态加载的脚本完成下载后,又第一时间开始了脚本执行。

图片

图片

lifecycle 和 paint timing

下图展示的是文章中提及的页面生命周期流程图。本节我们结合 Performance,对照该图进行观察。

图片

beforeunload

因为 Performance 的录制是在已有页面上进行 reload,所以记录的生命周期从页面的卸载开始。如下图 Main 所示,beforeunload 事件首先被浏览器触发。可以注意到,黄色条 Event: beforeunload 是浏览器自身触发的活动,我们称之为根活动(Root activities)。

图片

pagehide

从下图中我们可以注意到,为什么事件的触发顺序和上面的生命周期流程图不一致,是 pagehide -> visibilitychange -> unload 呢?事实上,在浏览器之前的设计中,如果页面在卸载阶段可视,visibilitychange 就会在 pagehide 之后触发,正如下图截图中一样。这就使得页面的卸载在不同可视情况下,有着不一致的生命周期与事件顺序,给开发者带来复杂性。

图片

在未来新版本浏览器中,卸载阶段的事件顺序会进行统一,目前进度在这一 issue 下。也正因为这部分的调整,unload 已经不建议在代码实现中使用了。

first paint

首先区分下以下两个时间点:

  • first paint:指的是首个像素开始绘制到屏幕上的时机,例如一个页面的背景色

  • first contentful paint:指的是开始绘制内容的时机,如文字或图片

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!**

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值