关于前端:浏览器是如何首次渲染网页的

44次阅读

共计 2978 个字符,预计需要花费 8 分钟才能阅读完成。

前言

我在学习 html 的过程中,承受到最多的倡议就是把 style 标签写在头部,script 标签写在开端,如同标准就是如此。

明天就来探讨一下,这样子书写到底为何?浏览器如何将 html 文档一步步绘制为绚丽多彩的页面的?

主流程

浏览器从承受到 html 文件,到显示出富丽的页面,一共经验以下 6 步:

  • 解析 HTML

    • 将二进制数据转换成 “UTF-8” 或 “unicode” 字符串
    • 将字符串转化成 Tokens
  • 构建 DOM 树

    • 将 Tokens 解析成 DOM 节点。
    • 将 DOM 节点组合成一棵 DOM 树。
  • 构建 CSSOM 树

    • 通过 Tokens 中的 stylelink 标签获取 CSS 文件。
    • 解析 CSS 文件,构建出对应的 CSSOM 树。
  • 构建 Render 树

    • 联合 DOM 树与 CSSOM 树构建出一棵 Render 树。
  • 布局 / 回流

    • 计算出每个元素的准确地位。
  • 渲染 / 重绘

    • 逐像素的将元素渲染到屏幕上。

Webkit 次要流程

Gecko 次要流程

尽管 Webkit 和 Gecko 应用的术语略有不同,但流程基本相同。

解析 HTML

看到下面的主流程时你可能会纳闷,在解析 html 时,为什么又要将字符串转化成 Tokens?Tokens 是个啥?间接解析 html 字符串不行吗?

别急,咱们都晓得,html 这门语言的语法十分宽容。咱们在编写 html 的过程中,不论咱们写了啥,浏览器都会失常的显示,素来没有遇到过“有效语法”这样的谬误。

这是因为浏览器承受到咱们的错误代码,它脚踏实地地修复了有效内容并持续工作。

例如在 html 文件中写下这样的代码

<body>
  <div>
    ddddd
  <p>
    ppppp
  </div>
  </p>
</body>

很显著的语法错误,短少 htmlhead 标签,divp 标签互相嵌套,而在浏览器中显示的后果却是这样的

浏览器主动为咱们增加了 htmlhead 标签,敞开 divp 标签.

正因 html 语法容许程序员犯错,浏览器就必须有一个纠错的过程,这就是须要先转化成 Tokens 的起因。

同时,切片为一段段的 Tokens,很适宜接下来渐进式的解析、渲染,给用户带来更好的体验。

接下来浏览器会遍历所有 Tokens,将内容分发给的 DOM、CSS 或 JS 解析器,供他们构建 DOM 树、CSSOM 树或执行脚本。

构建 DOM 树

这一过程会就将 Tokens 解析成 DOM 节点对象,为 DOM 节点绑定属性,而后增加到 DOM 树中。

比拟非凡的是,节点身上的内联款式,它们会调用 CSS 解析器进行解析,但不会参加到构建 CSSOM 的过程中,内联款式会作为属性保留在节点身上。

构建 CSSOM 树

浏览器通过 Tokens 中的 stylelink 标签获取 CSS 文件,而后通过 CSS 解析器解析文件,构建 CSSOM 树。

须要留神的是,CSS 解析只有获取了残缺的 CSS 文件,才会开始解析。

因为 CSS 款式之间存在互相笼罩,很可能后面解析了许多内容,全被前面的款式笼罩了。

也要晓得,真正的 CSSOM 树并不只有咱们写的款式,浏览器本身还带有大量的默认款式。

构建 Render 树

浏览器联合 DOM 树与 CSSOM 树,生成真正在视图中所出现的 Render 树。

这一过程次要是过滤掉 DOM 树中无需渲染的节点,比方 htmlheadmeta 等标签,或是款式设置为 display:none 的节点。

布局与渲染

依据 Render 树,为每个节点调配一个应呈现在屏幕上的确切坐标,而后将节点渲染到页面上。

浏览器运行时的优化会十分关注这一过程,但本文次要探讨的是首屏的加载,在这里不做过多探讨。

渐进式的解析

浏览器解析 HTML 这是一个渐进的过程,为达到更好的用户体验,浏览器会力求尽快将内容显示在屏幕上。

浏览器不用等到整个 HTML 文档解析结束,就会开始构建 Render 树和设置布局。在一直承受和解决来自网络的其余内容的同时,浏览器会将局部内容解析并显示进去。

正因浏览器的解析是渐进式的,咱们心愿用户一开始看到的 DOM 元素都在其最终地位上,所以会先把 style 标签写在头部,先构建完 CSSOM 树,再渐进式地构建 DOM 树。

解析过程中阻塞

浏览器是从上至下解析 HTML 的,生成的 Tokens 标签也是按此顺序排列的。

而后浏览器顺次遍历每个 Tokens 执行对应的解析器。

自身解析 DOM、解析 CSS、运行 JS 之间并无关系,轮到谁就先解析谁。

但用户看到的都是 DOM 元素,所以程序员就最器重 DOM,常说 CSS、JS 阻塞了 DOM 树的构建。

在 JS 中,可能会扭转以后 DOM 树或 CSSOM 树的构造,而之后又要获取最新的状态,浏览器就会进行 JS 的运行,批改 DOM 树或 CSSOM 树,从新布局计算元素地位。所以真正被阻塞的,应该是 JS。

当初咱们来看一个例子来加深阻塞的意识

<script>
  const n = document.getElementById('div')
  console.log(n)
</script>
<style>
  div {width: 400px;}
</style>
<div id="div"></div>
<script>
  const div = document.getElementById('div')
  console.log(div.clientWidth)
  const style = document.createElement('style')
  style.innerHTML = `div {width: 200px;}`
  document.head.appendChild(style)
  console.log(div.clientWidth)
</script>

而控制台的输入是这样的

null
400
200

咱们来剖析一下流程

浏览器从上至下解析,在第一个 script 标签时,执行 JS,但 DOM 树中并无 #div 的元素,获取不到节点,输入 null

而后遇到 style 标签,生成 CSSOM 节点,增加到 CSSOM 树中。

再遇到 div 标签,生成 DOM 节点,增加到 DOM 树中,此时曾经能够组合成 Render 树,在页面显示。

最初又遇到 script 标签,执行 JS,此时曾经有 #div 的元素,获取其宽度并输入。

而后这段 JS 还发明了一个 style 标签,插入 head 节点中(示例代码的上方)。这时 HTML 的构造产生了扭转,会为其生成 Token,解析其中的 CSS,更新出最新的 CSSOM 树,从新布局与渲染。

最初提供给 JS 最新的属性值,用以输入。

异步脚本

咱们如果在 linkscript 标签引入了内部文件,浏览器就会进行解析,始终等到内部文件下载实现后,才开始解析。

这显然是对资源的节约,于是便有了异步脚本,通过给script 标签增加 deferasync 属性实现。

  • 浏览器遇到带有 deferasync 属性的 script 标签,会立即开始下载,但不会阻塞 html 的解析。
  • 带有 defer 属性脚本的执行会推延到浏览器解析残缺个 html 文件,在 DOMContentLoaded 事件之前。
  • 带有 async 属性脚本的会在浏览器解析 html 时异步执行。
  • 多个异步脚本之间并不会按程序执行。
  • 异步脚本中不应该批改 DOM 的构造。

优化

弄清楚了浏览器渲染页面的流程与原理后,在这里提出几点优化的办法:

  • 书写符合规范的 html 代码,不要节约浏览器的资源去为代码纠错。
  • 重要款式间接写在 html 的 head 标签中,不重要的款式在开端书写或用 link 加载。
  • script 标签要写在开端,不影响 DOM 构造的脚本加上 defer 属性。

参考文档:
How browsers work

正文完
 0