Chrome 浏览器的 Performance 面板为咱们提供了检测页面性能的能力,但其提供的远不止一些性能数据。本文将从工做原理的视角,结合实际工程的录制结果,探一探性能面板向咱们透露的其余信息。javascript
关于面板的功能与使用方法,能够参考这篇 文章。本节主要介绍浏览器架构与性能面板的关系。
由于还没有决出最终的标准架构,各大浏览器的实现细节各有不一样。这里咱们以 Chrome 的架构为例,对照其架构与性能面板的关系。
由下图咱们能够看到性能面板呈现的几个主要线程。性能面板并不包含架构中所有的线程,主要仍是与页面渲染过程相关的部分。html
Network 表明浏览器进程中的网络线程,咱们能够看到时间轴上包含了全部的网络请求和文件下载的调用信息,并以不一样颜色标识不一样类型的资源。java
Main 表明渲染进程中的主线程,渲染相关的事情基本都是它来作,脚本执行、样式计算、布局计算、绘制等等。git
Compositor 表明渲染进程中的合成线程,Raster 表明渲染进程中的栅格线程。现在浏览器绘制一个页面,能够分为如下几步:github
咱们能够看到,在性能面板中主线程在最后调用了栅格线程作实际的渲染。web
显然,这部分就是 GPU Process 中的 GPU 线程。chrome
接下来咱们将大体从时间维度,看看浏览器录制下来的「工做报告」。api
咱们旅途的起点将从点击 Chrome Performance Panel 的 Reload 按钮(形如刷新)开始。当前页面首先进行卸载,伴随着几个日志上报,浏览器开始了 index.html 的下载工做。
HTML 文档下载完成后,浏览器开始按照 HTML 标准对 index.html 进行解析,在主线程中将接收到的文本字符串解析为 DOM 。咱们能够注意到,HTML 的解析过程并非一鼓作气,这是由于 HTML 一般还包括了其余外部资源,如图片、CSS、JS 等。这些文件须要经过网络请求或缓存来获取。其中,当 HTML 解析器解析到 <script>
标签时,HTML 文档的解析过程就会停止,转而去加载、解析和执行脚本。所以,从主线程的时间轴能够看出,Parse HTML 的过程是断断续续的。浏览器
如下处理策略均可以在主线程中看到,可是不一样资源的处理条长短差距较大,截图困难,这里不作呈现。
那么浏览器对不一样资源的处理策略是怎样的呢?缓存
总的来讲,浏览器对 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
脚本,以后很快开始了下载动做。动态加载的脚本完成下载后,又第一时间开始了脚本执行。
下图展现的是文章中说起的页面生命周期流程图。本节咱们结合 Performance,对照该图进行观察。
由于 Performance 的录制是在已有页面上进行 reload,因此记录的生命周期从页面的卸载开始。以下图 Main 所示,beforeunload 事件首先被浏览器触发。能够注意到,黄色条 Event: beforeunload 是浏览器自身触发的活动,咱们称之为根活动(Root activities)。
从下图中咱们能够注意到,为何事件的触发顺序和上面的生命周期流程图不一致,是 pagehide -> visibilitychange -> unload 呢?事实上,在浏览器以前的设计中,若是页面在卸载阶段可视,visibilitychange 就会在 pagehide 以后触发,正以下图截图中同样。这就使得页面的卸载在不一样可视状况下,有着不一致的生命周期与事件顺序,给开发者带来复杂性。
在将来新版本浏览器中,卸载阶段的事件顺序会进行统一,目前进度在这一 issue 下。也正由于这部分的调整,unload 已经不建议在代码实现中使用了。
首先区分下如下两个时间点:
从 Performance 中,咱们能够看出首次绘制的一系列动做(有些过程啪的一下很快啊,截图就省了):
Layout 以后的过程很快,这里放大些倍数来查看:
DOMContentLoaded 表示 HTML 已经彻底被加载和解析,固然样式表、图片等资源还不必定已经完成加载。从下图中能够看到,通过多段 HTML 解析后,DCL 以后就没有其余的 Parse HTML 了。
因导航而使得浏览器在窗口内呈现文档时,浏览器会在 window 上触发 pageshow 事件,具体的时机可参考这里。不只如此,当页面是初次加载时,pageshow 事件会在 load 事件后触发。
那么回到 Performance 的时间轴,从下图咱们能够看到,在红色虚线(标志着 load)以后,浏览器触发了 pageshow 事件,也就是上文说起的根活动。
比较惋惜的是,Performance 还没法清晰的看出 Event Loop。下图中灰色的 Task 并非指宏任务,其表明的是「当前主线程忙碌,没法响应用户交互」;Run Microtasks 则确实是在一次任务的末尾执行的微任务。当咱们点开调用栈观察时,能够看到源码中的回调函数以及对应的源码位置。
经过 Task 能够定位性能出现问题的地方。RAIL 模型告诉咱们须要重点关注占用 CPU 超出 50ms 的复杂任务,以提供连贯的交互体验。固然,这里更多的是对交互阶段的响应的要求,而不必定是对初始加载阶段的要求。
本文从工做原理的视角,结合实际工程的录制结果,进行了一次实践对理论知识的检验。Performance 不只是性能分析工具,仍是探究浏览器工做原理的小霸王学习机。总的来讲,浏览器的工做是充实且复杂的,与咱们打工人的摸鱼平常造成了对比,仍是须要进一步加深学习与思考呀。
[1] Measure performance with the RAIL model
[2] Get Started With Analyzing Runtime Performance
[3] Inside look at modern web browser
[4] JavaScript Start-up Performance
[5] How browsers work
[6] How the Browser Pre-loader Makes Pages Load Faster
文章可随意转载,但请保留此原文连接。
很是欢迎有激情的你加入 ES2049 Studio,简历请发送至 caijun.hcj@alibaba-inc.com 。