解析阻塞脚本
当浏览器碰到script元素,如果是一段内嵌脚本,浏览器停止HTML解析,立即执行该脚本,然后继续解析HTML。所有内嵌JavaScript都会阻塞HTML解析。
如果script是外部脚本文件,浏览器会停止主线程工作(停止DOM解析),去下载js文件并等待其完成下载。当js下载后,浏览器会先执行下载的文件,然后继续主线程的DOM解析工作。如果浏览器发现另外一个script标签,它也会做同样的操作。为什么浏览器要停止当前的DOM解析工作呢?
我们知道,浏览器从JavaScript运行时暴露了DOM API,意味着我们可以用JavaScript访问或操作DOM元素。这就是那些动态的web框架可以工作的原理。比如React, Vue, Angular...但如果浏览器同时运行DOM解析和执行JS,那就会产生竞争关系,因为俩线程都可能改变DOM,最终导致DOM树不准确,所以DOM解析和JS执行都必须在主线程上。
尽管如此,当下载JS文件的时候,停止DOM解析在大多数情况下是完全没有必要的。所以HTML5增加了async属性给script标签。当浏览器碰到带async的script标签,它下载JS文件的时候不会停止DOM解析,一旦下载结束,就会阻塞DOM解析并且立即执行JavaScript代码。
我们还有一个更好用的defer属性,它跟async类似,下载的时候不会阻塞DOM解析。不一样的是,当defer文件下载完毕后,不会立即执行,会等到DOM树完全构造完成后才会执行。
在上面的例子中, parser-blocking.html文件,在30元素后,有一个阻塞解析的script, 这就是为什么浏览器开始显示了30个元素,然后停止了DOM解析,开始下载JavaScript文件。第二个script文件有defer属性,所以它会在DOM树完全建立后才执行。
如果看性能面板,浏览器一开始构造DOM树,有了一些HTML内容时,FP和FCP就发生了。我们就看到一下东西呈现出来了。
LCP发生于5秒后,因为需要处理JavaScript,所以DOM解析就会被阻塞5秒(JS文件的下载时间),并且只有30个文本元素被显示。但是这些东西还不足以成为最大的渲染内容(根据Google标准)。一旦JS文件下载并执行完成, DOM解析恢复,这时最大内容会被显示出来,所以LCP就触发了。
渲染阻塞-CSS
我们已经知道,任何其他外部资源,除了JavaScript文件,都不会阻塞DOM解析。所以CSS也不会直接阻塞DOM解析。。。等等。。。不会直接阻塞!!!什么意思?其实CSS会阻塞DOM解析,但我们需要先了解渲染过程。
浏览器引擎会将HTML文本变成DOM树,并且它也从stylesheet可以构造CSSOM树。但是DOM树和CSSOM树的构造都是在主线程上进行的,然后它俩合并成渲染树。我们已经知道DOM树是生成是增量的,一边读HTML一边生成节点添加到树上。但这不是CSSOM的构造过程,CSSOM树构造不是增量型的,是必须在某一特定时候发生的。
当浏览器发现块,它会解析所有的内嵌CSS并且更新CSSOM树,然后继续进行正常的DOM解析,inline样式也一样的处理。
然而,如果碰到外部CSS文件,事情就大不一样了。不像外部JavaScript文件,外部CSS文件不是解析阻塞资源,所以浏览器可以在后台继续下载,DOM解析仍然会继续。