浏览器响应用户操作,获取document

我们在地址栏键入地址,到看到完整的页面,这个过程中浏览器都经历了写什么?

  1. 查询DNS得到相应ip地址
  2. 与ip对应的服务器握手建立TCP连接
  3. 向服务器发起HTTP请求
  4. 服务器做出相应并返回document
  5. 浏览器接收相应并下载html字符串,随即开始渲染

本文主要讲的就是第5点,浏览器如何渲染html文档。

浏览器得到document,开始渲染

浏览器拿到html文档后,会按照 从上到下 的顺序解析

  1. 解析HTML生成DOM
  2. 如果有CSS,则同时解析CSS,并生成CSSOM
  3. 合并DOM和CSSOM为渲染树
  4. 根据渲染树计算出节点的大小位置层级关系等
  5. 将节点绘制在浏览器中

这里有几个点需要特别注意:

  • CSS的加载会阻塞浏览器渲染过程

也就是说,1和2并不总是严格按照该顺序执行——当解析HTML时,遇到<link>标签,会暂停解析,优先等待浏览器下载css文件,并构建完成CSSOM,才会继续解析。

  • 事实上,页面总会有可执行的javascript,而javascript会阻塞浏览器渲染

同样的,遇到<script>浏览器会暂停解析,并中断DOM构建,先等待js下载完成,然后执行javascript脚本,执行完成后才继续解析。

  • CSS阻塞的优先级比javascript高

如果有CSSOM正构建,那么会延迟javascript的执行,直到CSSOM构建完毕。

  • 阻塞的是构建DOM的过程,不会中断资源加载

当DOM构建过程被阻塞时,浏览器会继续往下扫描资源并下载。

所以,我们得到:

  • 如果没有javascript,在DOM构建完成后便触发DOMContentLoaded事件
  • 如果没有css,会按照从上到下的顺序执行完javascript后,才能构建完成DOM,并触发DOMContentLoaded
  • 如果同时存在css和javascript,会在等待css加载完成后,开始顺序执行javascript,直到构建完整个DOM,才会触发DOMContentLoaded

由于浏览器的这种渲染机制,所以我们通常把css放在开头,把script放在靠后。

为了更早的呈现页面给用户,我们可以对<link><script>标签做些设置,让它们不阻塞渲染。

1
<link media="print">

只有打印时才加载

1
<link media="max-width: 300px">

只有屏幕小于300px才加载

详见:CSS媒体查询

<script> 延迟与异步

1
<script src="script.js" defer></script>

遇到这样的script并不会中断DOM构建,而是另外一条线程来加载该script.js,等到DOM构建完毕且js下载完毕,不要求顺序,只要同时满足时,便会执行javascript代码,之后才触发DOMContentLoaded

1
<script src="script.js" async></script>

相同的,加载时不会中断DOM构建。与defer不同的是,不会等到DOM构建完毕,只要下载完毕就立即执行。如果执行时DOM尚未构建完毕,那么就会中断DOM构建先执行javascript,等构建完毕后触发DOMContentLoaded;如果DOM已经构建完毕,会先触发DOMContentLoaded,后执行javascript,最后才能触发load

不论是defer还是async,都将不等待CSS加载完毕,直接执行javascript。也就是说,这种做法可以将javascript的优先级提高到css之前。

参考:<script>