用performance分析网页的渲染,我悟了!

一、写在前面

我发现即便工作很久了,我还是不会用performance性能分析工具,可能很多小伙伴跟我有一样的问题!

于是我怒学了一通performance性能工具的使用,想写一篇如何使用performance工具的教程的,但是我发现字节团队的这篇文章已经写的很好了。

掘金不需要多一篇一模一样的文章,所以我本来打算放弃写类似的文章的,但是最近工作中正好涉及到网页性能相关的知识,我发现网上的文章说法的各不相同,就比如这个问题:

css和js到底阻不阻塞DOM?如何阻塞的?细节是什么?原因是什么?

如果你对这个问题感觉比较模糊,能说个大概,但是不清晰!那么接下来就是你看这篇文章的意义!

本篇文章尝试从performance性能分析工具的角度来探讨一个网页从url到渲染完毕发生的所有事情,并且信仰口说无凭,要么拿出实验证据,要么有performance的铁证,因为网上的信息太多了,众说纷纭,我们只能自己尽可能找到最贴近真相的答案。

本篇文章会从performance性能分析工具的角度带着大家分析一个网站的解析,即便没有任何performance基础都可以,我会在文中会提到相关的知识!

二、再谈渲染进程

chrome浏览器是多进程架构,也就是说每一个新开的tab都是一个独立的进程,然而除了浏览器的每一个tab的渲染进程之外,还有浏览器进程、GPU进程、插件进程、网络进程等和浏览器进程共同协作!

v2-df324ff63b24b53eb5ac532e06e222ff_1440w.png

我们可以通过chrome浏览器的任务管理器查看目前浏览器的进程占用内存和CPU的情况:

截屏2023-06-29 上午8.17.05.png

查看方式是:右上角菜单 -> 更多工具 -> 任务管理器

而我们最需要了解的就是渲染进程了,也就是浏览器的内核,它负责将html、css、js等静态的资源变成可交互的用户界面。

渲染进程由GUI线程、js引擎线程、webWorker线程、合成线程、光栅线程组成、预解析线程等组成。GUI线程负责解析HTML以及CSS、计算样式等,js引擎负责解析和执行js脚本,webWorker线程负责解析和执行webWorker中的js程序,合成线程负责合成图层,以便提升GPU绘制的性能,光栅线程负责将renderTree信息转化为像素信息,预解析线程负责提前扫描一下提前下载资源。

三、上手performance

接下来我们就用performance来记录掘金主页的渲染过程,并且详细分析其存在的各种知识点,为了方便理解,我把devTools调试为中文的翻译。

在上手之前,你也可以阅读这篇文章,以便更好的理解下面的内容。

通过性能分析工具,我得到了一张这样的performance性能分析工具图,这里要说明一下,由于网络和硬件的不同,读者朋友们的图可以和我的有很大不同。

截屏2023-06-29 上午8.49.27.png

performance在录制的过程中会从各个进程里读取数据,汇总到一起生成了这样的一份动态的报告!

截屏2023-06-29 上午8.54.04.png

可以看到在宏观上,网络进程、渲染进程、GPU进程是同时在工作的,互不影响,这更加佐证了浏览器多进程架构的特点。

接下来我们分析一下如何从一个url变成页面的,故事还是得从网络说起!

网络

当我们的时间来到第一刻的时候,便是请求html资源,当我们向服务器请求这个资源的时候,会经历很多过程,例如DNS解析、建立连接、可能会发生重定向等。

当我们在看performance性能分析工具的时候,如果没有那么多时间全部都操作一遍,掌握两个地方就可以了,一个是网络,一个火焰图。

截屏2023-07-01 下午6.37.12.png

上面是我截取的一部分网络的过程,可以看到那个蓝色的就是juejin首页的html资源的加载的过程,整个过程由网络IO进程负责。一般来说,有以下的规律:

颜色 含义
蓝色 html资源
紫色 css资源
橙黄色 js资源
绿色 图片资源
灰色 json资源

并且每一个资源的加载过程都有这样的图表示:

截屏2023-07-01 下午6.43.26.png

他们分别?️左侧线条、浅色区别、深色区域、右侧线条组成,读者朋友可以再翻上去看一下是不是这样的感觉,或者在自己的performance中感受一下!

他们代表的含义就是:

图形 含义
左侧线条 网络进程队列、DNS、建立连接的耗时
浅色区域 等待服务器响应的耗时
深色区域 下载资源的耗时
右侧线条 等待渲染进程接管的耗时

我们也可以在网络中看到每一个资源加载的详细过程:

截屏2023-07-01 下午7.19.22.png

可以看到其实首页加载主要的耗时还是在等待服务器响应这一块,这是网络方面的瓶颈!

预解析

在下载首页html资源的时候,其实并不是下载完了渲染进程才开始做事情,其实渲染进程从开始接受第一个html字节流的时候就开始准备要做事情了!

这个事情就是预解析,预解析是浏览器为了提升网页渲染性能而设计的,我们可以设想一下,因为js的加载和执行都是阻塞DOM的解析和渲染的,因此如果一定要等到html资源加载完毕,才开始下载js以及其他资源的话,那么IO的时间消耗就会更加的明显,于是浏览器在渲染进程里设计了一个小线程,专门在解析html之前,就预解析一遍html,将需要下载的资源并行的加载,这样的话就提前规避了很多阻塞的问题!以提升网站性能!但是预解析是从什么时候开始的呢!

还是用performance性能分析工具来分析一下吧!

截屏2023-07-01 下午6.54.56.png

我们可以看到css、js资源是在深色部分就开始了请求,那么我们可以得出一个结论,预解析的过程并不会等待所有html资源都下载完毕放在内存才开始工作,而是从接受html资源第一个字节开始就准备进行预解析的工作了!

实际上正确的过程是当html资源接受第一个字节开始,网络进程就开始和浏览器进程通信,他们的对话就像下面这个样子!

网络进程:嘿!浏览器老哥,我这边开始进货了,你那边要不要通知一下渲染进程啊!

浏览器进程:好的,老弟,我马上给渲染进程打电话哈!说罢:浏览器给渲染进程打电话….

渲染进程:怎么了,浏览器大哥,有啥事么!

浏览器进程:那啥,网络那边开始进货了,你这边啥时候准备验货啊。

渲染进程:可以呀!我时刻准备着呢,不过我就不亲自动手了,我派个预解析线程先简单验一下。你直接叫网络那边给我一箱、一箱送货就行。

浏览器进程:好的,没问题!

于是网络进程那边不断给渲染进程送上html资源,而预解析线程就开始扫描所有可能发送请求的标签,比如没有添加perload、perfetch属性的link标签、没有async和defer的script标签、带src的img标签等等!

结论:预解析的过程并不是在html资源下载完了之后才进行,而是在下载过程中就开始了。

js、css阻塞DOM解析么?

这个问题实际上应该拆解成:js的下载和执行,以及css的下载和解析分别影响DOM的解析么?

我们分别来看:

js:

我们准备这样一个html

<!DOCTYPE html>


<html lang="en">


  <head>


    <meta charset="UTF-8" />


    <meta http-equiv="X-UA-Compatible" content="IE=edge" />


    <meta name="viewport" content="width=device-width, initial-scale=1.0" />


    <title>Document</title>


  </head>

  <body>

    <h1>我是script之前的h1</h1>
    <script src="http://localhost:5501/block.js"></script>
    <h1>我是script之后的h1</h1>
  </body>

</html>

// block.js
const now = () => performance.now();
const time = now();
while (now() - time <= 2000) {}

屏幕录制2023-07-04 下午10.39.32.gif

如果我们做上面的这样的实验,就可以看到一个现象!(注意把网络调成低速3G会更加明显),首先界面上出现了“script之前的h1”,等了4s之后绘制出了“script之后的h1”,我们简单分析一下这个现象:

其中2s是block.js脚本执行的时间,因为我们的脚本就是这个效果,那么另外的2s中就是在低速3G的情况下block.js这个文件的网络耗时,说明了一个问题,js对于DOM的阻塞效果至少在用户来看,是js的下载、解析、执行都会阻塞DOM的渲染。

所以我们通过这个简单的实验,可以得出至少第一个结论:js的下载和js的执行都会阻塞DOM的解析和渲染。

但是事实真的是这样子的么?当我们掌握了performance性能分析工具之后,我们可以进一步看看到底是不是这样的!我们判断的核心依据应该是在资源加载的这个过程中,解析HTML和绘制有没有进行,如果有进行,那么就js是不阻碍DOM的解析和渲染的。

截屏2023-07-05 下午12.43.18.png

我们可以看到在下载js文件资源之前,渲染进程都会先进行一次解析和渲染,这是为了把script标签之前的内容先渲染出来,以便获得更好的用户体验,

截屏2023-07-05 下午12.49.14.png

可以看到js在下载资源的过程中,是不会出现解析DOM和渲染的工作的,所以再一次印证了上面提到的观点。

css:

css一般情况下我们指的是使用link标签外联的css样式,因为内联的css不存在下载这个过程,所以不用谈论下载对DOM解析造成的影响,我们要研究的问题是css外部资源在加载和解析过程中是否会阻塞DOM的渲染。

准备下面这样的样例:

<!DOCTYPE html>


<html lang="en">


  <head>


    <meta charset="UTF-8" />


    <meta http-equiv="X-UA-Compatible" content="IE=edge" />


    <meta name="viewport" content="width=device-width, initial-scale=1.0" />


    <title>Document</title>


    <link
      rel="stylesheet"
      href="https://necolas.github.io/normalize.css/latest/normalize.css"
    />
  </head>
  <body>
    <h1>我是link标签之后的DOM结构</h1>
  </body>
</html>

效果如下:

屏幕录制2023-07-05 下午9.49.27.gif

通过实验现象,我们可以看到过了几秒后,界面上才出现了DOM,因此通过这个现象我们就可以得出结论,css的加载过程阻塞DOM的渲染,也就是绘制!

但是阻塞解析么?要知道,渲染和解析是两个概念,解析是指将静态的元素标签解析为内存中的一种数据结构,并维护好子父级的关系。而渲染是指将解析后的DOM结构绘制为界面上可见的视图。

这个时候我们依然借助performance工具来看一下是否阻塞解析:

截屏2023-07-05 下午9.55.35.png

从performance性能分析工具的体现来看,在css还在网络过程中,依然存在DOM解析的任务进行,因此可以看出css的网络过程不会阻塞DOM解析,但是会阻塞渲染,因为一般来说解析和渲染是先后会出现的,大家经常做performance分析的话就会发现这一现象,但是在第一次解析DOM的过程中并未存在渲染的任务,相反在css下载结束之后,这个时候解析DOM后才出现渲染,更加证明了css的网络过程会阻塞DOM的渲染,下面是performance证明:

截屏2023-07-05 下午10.05.44.png

因此我们可以通过以上的实验得出以下结论:css的网络过程阻塞DOM的绘制,但不阻塞DOM的解析。

宏任务、微任务

我们都知道在js当中存在宏任务和微任务的概念,可能在之前我们都是通过看文章来理解这个概念,但是实际上在performance中,我们可以以肉眼可见的方式去观测宏任务和微任务的执行过程!

我们先来回顾一下,创建宏任务和微任务的方式有哪些!

宏任务 微任务
setTimeout Promise
setInterval MutationObserver
MessageChannel nextTick(node)
setImmediate process(node)
I/O process(node)

其实不仅仅有上面这些,创建宏任务的方式还有 script标签、事件回调、DOM解析、系统内程序

下面我们通过一个案例并做一次性能分析来进行详细的说明:

<!DOCTYPE html>


<html lang="en">


  <head>


    <meta charset="UTF-8" />


    <meta http-equiv="X-UA-Compatible" content="IE=edge" />


    <meta name="viewport" content="width=device-width, initial-scale=1.0" />


    <title>Document</title>


  </head>

  <body>

    <script src="./first.js"></script>
    <script src="./second.js"></script>
    <button onclick="macro()">创建宏任务</button>
  </body>

</html>

//first.js
const microTask = (from) => {
  console.log(`我是由${from}一个微任务`);
};

const macroTask = (from) => {
  console.log(`一个由${from}创建的宏任务`);
};

const macro = () => {
  console.log("一个click事件创建的宏任务");
};

macroTask("script");

Promise.resolve().then(() => {
  microTask("promise");
});

setTimeout(() => {
  macroTask("setTimeout");
}, 10);

//seond.js
const messagechannel = new MessageChannel();
const port = messagechannel.port1;
messagechannel.port2.onmessage = () => macroTask("messagechannel");
port.postMessage(null);

通过以上的可以演示大部分宏任务的创建方式,他们的performance是这样的:

截屏2023-07-05 下午10.41.56.png

其中每一个灰色的任务都是一个或多个独立的宏任务,他们是紧密相连的,这个就是事件循环,灰色下面的第二层每一个独立的块都是一个独立的宏任务,宏任务可以是黄颜色的代表js任务、蓝色则代表解析html的任务、绿色代表绘制也是一个宏任务。在一个宏任务下面如果有微任务,那么会放在末尾执行,如下图所示:

截屏2023-07-05 下午10.45.59.png

所以如果今后当我们看到某一个任务比较长的时候,performance也会标红,这个就是我们需要优化的空间,因为长时间的纯js的宏任务会阻塞DOM的渲染。

时间线

实际上在performance性能分析工具上有这么几个时间点非常重要,他们分别是:

简称 全称 含义
FP First paint 页面在导航后首次呈现出不同于导航前内容的时间点
FCP First Contentful Paint 首次绘制任何文本,图像,非空白canvas或SVG的时间点。
DCL DOMContentLoaded HTML加载完成的时间点
L onLoad 页面所有资源加载完成的时间点
LCP Largest Contentful Paint 可视区域“内容”最大的可见元素开始出现在页面上的时间点。
FMP First Meaningful Paint 首次绘制页面“主要内容”的时间点。

下图展示了从浏览器卸载旧页面到新页面加载完成的整个过程,每个页面事件穿插在每个页面加载阶段之间的过程:

下面我们来看一下掘金的这个几个事件节点所处的位置:

Snipaste_2023-07-06_18-52-14.png

  • 第615毫秒时,开始绘制第一个像素点,紧接着第一个文本开始绘制,FP和FCP是在一起的,并且FP一定永远都在最前面。

  • 第1231毫秒时,HTML加载完成

  • 第3072毫秒时,所有资源加载完毕

  • 第4124毫秒时,页面“主要内容”绘制完成

三、参考资料

Chrome DevTools Performance 功能详解
快速掌握 Performance 性能分析:一个真实的优化案例
现代浏览器架构
inside-browser-part1
原来 CSS 与 JS 是这样阻塞 DOM 解析和渲染的

四、最后的话

以下是我的其他文章,欢迎阅读

保姆级讲解JS精度丢失问题(图文结合)

shell、bash、zsh、powershell、gitbash、cmd这些到底都是啥?

从0到1开发一个浏览器插件(通俗易懂)

用零碎时间个人建站(200+赞)

另外我有一个自己的网站,欢迎来看看 new-story.cn

创作不易,如果您觉得文章有任何帮助到您的地方,或者触碰到了自己的知识盲区,请帮我点赞收藏一下,或者关注我,我会产出更多高质量文章,最后感谢您的阅读,祝愿大家越来越好。

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYx0z66D' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片