[译]从输入URL到页面呈现的超详细过程——第二步:Tags转化成DOM的过程

原文链接:Tags to DOM
原文作者:Travis Leithead
译者:wangds

这是一个系列文章,分为四个部分介绍了从输入URL到页面呈现的详细过程

  • 客户端从服务器获取资源(Server to Client)点击查看
  • 标签转化成DOM的过程(tags to DOM)
  • CSS解析的过程(braces to pixels)
  • 编译执行javascript的过程(var to JIT)

本篇翻译的是第二部分:Tags to DOM

在上一篇文章中(客户端从服务器获取资源),我们谈论了资源是如何从服务器到达客户端的,同时也阐述了一些关于缓存、同源的概念。本篇文章将聊一聊HTML资源是如何转化成DOM tree的。

解析的过程(Parsing)

当浏览器获得了资源以后要进行的第一步工作就是解析,整个解析的过程可以拆分成如下四个步骤:

  1. 解码(encoding)
  2. 预解析(pre-parsing)
  3. 符号化(Tokenization)
  4. 构建树(tree construction)

1.解码(encoding)

客户端接收的内容可以是HTML文本、图像等等任何格式的数据。在从服务器到客户端展示的过程中,数据经过了如下的过程:

  1. 服务器将数据转换成bit
  2. bit进行数据传输
  3. 客户端将bit转化成数据

所以客户端必须知道数据转化成bit的格式,以便客户端将bit反转成正确的数据。

我们以HTML举例来说,HTML的文本格式有很多种,所以服务器在返回数据的时候在响应头中使用Content-Type告知浏览器文本的内容类型,同时在文本文件头部使用Byte Order Mark告知浏览器编码格式。如果浏览器仍然判断不了解码方式,浏览器还会自行匹配一种解码格式来处理数据。有时候,解码格式也会写在<meta>标签中。这就导致会出现一种奇葩的现象,浏览器先是以自己的方式去解码文本,然后遇到了标明解码方式的<meta>标签。这时候浏览器就会重新解析按照<meta>标签进行解析。

我们现在经常在HTML中使用的文件格式是UTF-8,那是因为UTF-8能较完整的支持Unicode字符范围,同时与CSSJavaScript中常见的节字符具有良好的ASCII兼容性。一般浏览器默认的解码格式也是 UTF-8。当解码出错的时候,我们会看到屏幕上全部都是乱码字符。

2.预解析(Pre-parsing/scanning)

完成了解码以后,浏览器会开启一个预解析的过程,“偷偷获取”后面需要加载的资源,减少处理的时间。预解析的过程不是一个完整的解析过程。举例来说,预解析不会处理HTML内元素之间嵌套的父子层级。但是预解析还是能识别一些特殊的HTML标签、元素属性值以及URL。假如我们的页面中有个img(如下),预解析就会注意src属性值,并将获取这个图片的请求加到请求队列中,尽早的将图片获取到本地以便显示。我们也可以通过prefetchpreload两种属性将需要尽早加载的资源告知于浏览器。

<img src="https://somewhere.example.com/​images/​dog.png" alt="">
复制代码

3.符号化(TOKENIZATION)

符号化是将输入解析成为符号,像开始标签(<)、结束标签(>)、注释(//)、文本内容等等。然后这些符号将被送到解析的下一个步骤。完成符号化工作的机器叫做符号识别器( tokenizer),符号识别器是一种状态机。 我们以<video controls>的标签为例:

  • 状态机初始状态是Data State,
  • 当遇到<的时候,状态变成Tag open state
  • 当解析到字母v的时候,状态变成Tag name state
  • 当解析到字母c的时候,状态变成in attribute name state
  • 解析完字母s的时候,状态变成after attribute name state
  • 解析完>的时候,状态变成Data state

从解析<video controls>标签我们就可以知道,整个页面会碰到很多标签,符号识别器就会不断的重复状态变化。(下图中的箭头就是状态机处于的某个位置)

avatar

HTML标准目前为符号识别器定义了80个独立的状态。在解析的过程中,符号识别器可以将任何的文本转化成HTML文档(即便文本内容不是有效的HTML)。这种弹性的机制使得HTML更加的易于开发,但是也可能导致因为写错标签出现特殊的bug。

对于那些更喜欢使用‘非黑即白’验证规则的人,浏览器内置了一种替代解析机制,可将任何解析错误视为灾难性的故障(出现错误就会导致无法呈现内容)。 这种解析模式使用XML规则来处理HTML,我们可以通过设置MIME类型的值为application / xhtml + xml开启这种解析模式。

浏览器可以同时处理预解析和标记化两个过程(也就是说这是两个线程)。

4. 构建树(tree construction)

在上一步符号化以后,解析器获得这些符号标记,然后以合适的方法创建DOM对象并将这些符号插入到DOM对象中。DOM对象的数据结构是树状的,所以这个过程称为构造树(tree construction)。顺便说一句,在IE的历史中,大部分时间里没有使用树结构。

avatar

另外现代浏览器为了兼容旧版本HTML,解析的过程还是十分复杂的。例如浏览器可以自动闭合标签。

<p>sincerely<p>The authors</p> //HTML的样子

<p>sincerely</p><p>The authors</p>//浏览器解析出来的样子
复制代码

我们可以使用JavaScript去任意修改DOM。假如我们用JavaScript进行无意义的操作(例如在video元素内插入一个table单元格),渲染引擎可以甄别出这些操作不合规,也不进行渲染(但是这个过程渲染引擎也进行工作只是不展示)。

因为JavaScript可以在DOM中添加内容。所以遇到JavaScript文件的时候,DOM将停止解析;如果JavaScript文件内调用了document.writeAPI,解析器将重新开始解析过程。

事件(Events)

当整个解析的过程完成以后,浏览器会通过DOMContentLoaded事件来通知DOM解析完成。事件相当于一套广播系统用来通知和监听浏览器。除了DOMContentLoaded事件,还有load事件(表示所有资源已经加载完成,包括图片、视频、CSS等等)、unload事件表示界面即将关闭、鼠标事件键盘事件等等。

浏览器在DOM对象中还会创建一个事件对象,该对象包含大量有用的信息(如屏幕中的坐标、按下的按键等等)。当JavaScript触发事件的时候,就会同时产生事件对象。

在目标元素上触发事件的时候,需要从DOM树的根元素开始向子元素查找,这个过程俗称事件捕捉阶段。到达目标元素以后,还要逐级向上返回到根元素上,这个过程俗称事件冒泡阶段。

avatar

事件是可以进行关闭的,我们可以在事件捕获或者传播的过程中选择关闭,例如在form的提交过程中判断字符串为空则关闭提交。

DOM

解析器解析出来的元素可以包含一些基本的交互功能(<video>能播放视频、<button>按钮等),但是这些基本的交互功能还不能满足我们的需求,我们需要使用cssJavaScript将页面展示的更加丰富多彩,而DOM就是HTML元素与其他资源交互的桥梁。

元素的接口

在解析器将元素放入DOM树之前,解析器会根据不同元素的名称赋予元素不同的接口功能。

下面是一些通用的功能:

  • 可以访问元素内的子元素集合
  • 可以搜索子元素的属性和方法
  • 最重要的是,可以不通过解析器添加新的元素到DOM

<table>元素具有查找删除表内所有单元格的能力;<canvas>具有描绘线条、图形的能力。但是想要调用这个接口还是需要JavaScript来操作。

每当我们使用JavaScript操作DOM的时候,将会触发浏览器的一些连锁反应,这些反应是为了让更改后的页面更快的呈现在屏幕上。例如:

  • 用数字代表通用的元素名称和属性,浏览器用使用哈希表进行快速识别这些数字
  • 将频繁变更的子元素进行缓存,方便子元素快速迭代
  • sub-tree的跟踪变化降到最低,避免‘污染’整个DOM

DOM中的HTML元素是页面展示的基础,DOM的能力也不仅仅局限于上面我们提到的功能。其他诸如访问储存系统(浏览器中的缓存)、设备功能(蓝牙、定位)、以及3D图形绘制等等功能。随着浏览器的不断进步和web标准的不断实施,DOM的功能只会越来越强大,本文只涉及了一部分而已。

结束语

以上就包含了标签转化成DOM的过程。下一篇文章我们将聊聊CSS解析的过程。

限于本人水平有限,文中如有错误感谢您指正。

猜你喜欢

转载自juejin.im/post/5c72a870e51d4541e37db4b3