前端渲染过程的二三事

前端渲染过程的二三事

本文不会介绍整个前端渲染过程的步骤,只是记录最近阅读的文章的些许思考和感悟。(文章地址一(系列),文章地址二

希望大家在阅读这篇文章之前能将上述文章仔细浏览一篇,因为本文所述基本是基于其内容。

Navigation Timing API和浏览器加载事件

Navigation Timing API:可通过打印(performance)查看;

浏览器加载事件:domContentLoaded,onload

前端渲染过程的二三事

上图整理了Navigation Timing API中的一些事件和浏览器加载事件的发生顺序。

这些事件的含义是什么呢?我们直接引用文章地址一(系列)的介绍:

  • domLoading:这是整个过程的起始时间戳,浏览器即将开始解析第一批收到的 HTML 文档字节。
  • domInteractive:表示浏览器完成对所有 HTML 的解析并且 DOM 构建完成的时间点。
  • domContentLoaded:表示 DOM 准备就绪并且没有样式表阻止 JavaScript 执行的时间点,这意味着现在我们可以构建渲染树了。

    • 许多 JavaScript 框架都会等待此事件发生后,才开始执行它们自己的逻辑。因此,浏览器会捕获 EventStart 和 EventEnd 时间戳,让我们能够追踪执行所花费的时间。
  • domComplete:顾名思义,所有处理完成,并且网页上的所有资源(图像等)都已下载完毕,也就是说,加载转环已停止旋转。
  • loadEvent:作为每个网页加载的最后一步,浏览器会触发 onload 事件,以便触发额外的应用逻辑。

看了上述事件的介绍,大致了解了每个事件所代表的含义,但也带着少许疑惑:

  1. HTML解析是分批进行的吗?为什么要分批进行?
  2. domContentLoaded事件结束后可以构建渲染树了,是否意味着CSSOM树构建也在该事件之前就已完成?
  3. domContentLoaded事件表示DOM准备就绪并且没有样式表阻止 JavaScript执行的时间点,可是JavaScript执行是在构建DOM树之前,那这是不是意味着该事件在DOM树构建完成后就执行了?若是这与domInteractive事件有什么区别呢?

首先看第一个问题,我们直接用chrome控制台的performance来看效果。

前端渲染过程的二三事
上图中,上方的Network的蓝条是HTML的下载时间,下方箭头所指是HTML的解析时间。

从中我们可以得出HTML解析的确是分批进行,并且解析并不需要等HTML完全下载完。那为什么要分批进行呢?这个问题我找了许多资料也没有得出结论,于是自己从中思考。假设不是分批进行,那实现会有两种情况:1.等HTML全部下载完,再一起解析;2.每下载一定量的HTML就将其放入解析器等待排队解析。前者首先肯定被排除,若HTML很大,会影响首屏加载速度,后者按理速度更快,或许其实现的难度以及可能会带来的一些问题而没有采用?这个问题思考了良久,感觉从理论上是可行的,但从技术角度,由于自己这方面的知识实在薄弱,所以也无法得知其实现的过程是否存在技术瓶颈。

第二个问题:CSSOM树构建是否在domContentLoaded事件之前?

话不多说我们自己实践。

首先我先创建一个HTML文件并写入少许标签,再创建一个CSS并于HTML引入,在CSS文件中写入大量内容(自己实践时写了5W多行)。然后用performance查看。

前端渲染过程的二三事

上图每个箭头代表的含义:

  1. HTML解析结束时间
  2. domContentLoaded加载时间
  3. CSS文件下载完的时间
  4. CSS文件解析的时间

从这个步骤以及其所处时间,我们可以清晰的得出,该结论不准确。那么该作者为什么会得出该结论呢,是他犯错了吗?我随后发现他在这篇文章下面还写了一句“domContentLoaded一般表示DOM和CSSOM均准备就绪的时间点”。那么这句话意味着大部分的时候CSSOM树的构建是在domContentLoaded事件之前。可是这个大部分又指的是什么情况,这又涉及到另一个知识点“DOM树,CSSOM树,JS的三角关系”,构造DOM树时遇见JS会先解析执行JS,而在解析执行JS时遇到CSSOM,又会先构造CSSOM树,这个过程稍后会具体说明。那么现在我们可以明白这个问题的关键所在了,因为在大部分页面中是拥有JS的,而由于其解析顺序,那么在domContentLoaded事件之前必定已经成功构造CSSOM树。

第三个问题:domInteractive与domContentLoaded的区别是什么呢?这两个事件中间是否还会进行其他操作?

我们都知道script标签有defer和async两个属性。有了这两个属性,浏览器就会加一个进程下载JS。那么下载完的执行时间点是在什么时候?其中async会在JS下载完后立马执行,也正是这个原因,会导致JS的执行顺序不一定按标签的从上至下,而是按照下载完的时间。那么defer属性的执行时间呢,我想大家应该都能猜到了,它的执行时间点的确就是在上述的两个事件之间,那么我们也就得知这两个事件的区别所在。

三大树

我们都知道前端渲染有三大树:DOM树,CSSOM树,RENDER树。那么这三大树的构造时间和上述的事件执行时间的顺序又是怎样的呢。

其中DOM树和RENDER树所在位置其实是显而易见的,并且在之前内容也已经指出。DOM树在domInteractive事件之前,RENDER树在domContentLoaded事件之后。但是CSSOM树就难以捉摸,其与DOM树的关系,完全受到是否拥有JS影响。

在确定CSSOM树所处位置前,我们先确定一个上面提到的概念:构造DOM树时遇见JS会先解析执行JS,而在解析执行JS时遇到CSSOM,又会先构造CSSOM树。官方给出的原因是,JS会使用document.write而改变DOM树,所以构造DOM树时碰到JS会先执行JS;而JS在执行时,需要查找CSS,所以执行JS时,碰到构造CSSOM树,会先构造CSSOM树。但是这里有一点奇怪的时,JS也可以通过创造Link标签的方式改变CSSOM树,所以个人感觉官方的这种解释有点牵强。不过官方的解释虽然不能让人完全信服,但这执行顺序是不会有错的。

接下来我们分别通过有无JS两大类确认CSSOM树所处位置:

  1. 无JS的情况
    由于DOM树和CSSOM树是并行解析的情况,所以这两个树构建完成的顺序完全无法固定,只由它们自己本身大小有关。因此CSSOM树构建完成的时间既可能在domInteractive之前,可能在domContentLoaded之后,也可能在这两事件之间。
  2. 有JS的情况
    这里我们先假设CSS文件很小,在没还解析到JS时就已完成解析,那么这种情况其实必定发生在domInteractive之前,因为都还没解析到JS,说明DOM树必定没有构建完成。那么再假设CSS文件很大,然后中途遇到JS文件,这时候JS文件发现在构造CSSOM树,其就会等待CSSOM树构建完成后再解析执行JS,所以这种情况CSSOM也必定在domInteractive之前。

    但是有2个有意思的情况:

    (1) 如果我们动态创建link标签并添加到html中,那么又会发生什么呢?若JS还没解析执行完,那么会停止JS而去解析CSS,若JS已执行完那么CSSOM树其实也不是必定在domInteractive之前。(当然这可能已经不算初次渲染构建)
    (2) link标签放到JS后面又会发生什么呢?这种情况我发现不管我怎么尝试,CSSOM树必定在DOM树构建之前构建,但其又在JS执行完成后。如果有兴趣的同学可以查查是为什么。

总结

其实前端渲染是一个很庞大的知识点,并且其涉及的周边知识也及其庞大,本文只是对其中一个小知识点做了思考和实践。最后要说的一点是,以上内容,纯属个人见解,如有不当,请多指教。

相关推荐