理解DOM

194 阅读4分钟

什么是DOM(Document Object Model, 文档对象模型)?

渲染引擎无法直接理解HTML这种文本信息,需要将文本信息转换为渲染引擎能够理解的结构化信息,这种结构化的信息就是DOM,即DOM是HTML的结构化描述。DOM不仅是生成页面的基础数据结构,也是连接JavaScript与Web页面之间的纽带。DOM向JavaScript提供了DOM接口,给予了JavaScript通过操作DOM,来改变页面的能力。

DOM是如何生成的?

在渲染引擎中,将HTML文本信息转换为DOM结构信息的工作,是由HTML解析器完成的。HTML文本信息一边加载,一边被HTML解析器解析。具体流程如下图所示:

未命名文件(16).png

网络进程接收响应后,根据响应头中的content-type来判断接收到的内容。当content-type的值为text/html时,浏览器就会认为收到的文件是一个HTML文件。为了解析这个文件,浏览器会首先选择或创建一个渲染进程,并在二者之间搭建一条数据流通的管道。网络进程收到什么,渲染进程也会收到什么。渲染进程收到HTML文件后,会将解析HTML的任务分配给HTML解析器对HTML进行解析,生成DOM。

HTML解析器是如何解析的?

未命名文件(16)(1).png

解析HTML的过程如上图所示。第一步,首先利用分词器将字节流转换为一个个Token。这些Token主要分为两类,一类是文本Token,一类是标签Token。为了方便后续的解析,对于双标签,我们将标签Token分为StartTag Token和EndTag Token。第二步,将Token解析为DOM节点,并将DOM节点添加到DOM树中。为了将文本信息转换为树型的结构信息,我们需要确定节点之间的父子关系。若仅存在文本Token与单标签Token,我们仅需要依次按序组织即可。但由于双标签Token的存在,因此在具体实现中,引入了Token栈。对于Token栈存在以下操作:

  1. Token为StartTag时,HTML解析器会创建一个DOM节点入栈,其父元素为入栈前栈顶元素;
  2. Token为EndTag时,HTML解析器会查看当前栈顶元素,若StartTag与EndTag标签相同,则出栈;
  3. Token为文本或单标签时,创建DOM节点不入栈,其父元素为栈顶元素;

为了方便表述,来看一段简单的HTML,模拟一下其解析过程。

<html>
<body>
    <div>DOM Node1</div>
    <div>DOM Node2</div>
</body>
</html>

HTML解析器在工作的时候,会首先默认创建一个根为document的空DOM结构。接着,将html body div的StartTag依次入栈,并创建对应的DOM节点,每个节点的父节点均为其入栈前的栈顶元素。 未命名文件(16).png 文本Token不入栈,直接创建文本节点,文本节点的父节点为当前Token栈顶元素。 未命名文件(16).png 文本节点创建后,HTML解析器解析到了div的EndTag Token,此时,EndTag与Token栈栈顶的StartTag的标签一致,均为div,则栈顶元素出栈。 未命名文件(16).png 按上述步骤,执行创建DOM树,并操作Token栈。 未命名文件(16).png

HTML解析时如何处理JavaScript?

在进行HTML解析时,JavaScript可能以两种方式出现,一是内嵌的JavaScript脚本,二是引入的JavaScript文件。

对于内嵌的JavaScript脚本。执行到script的StartTag Token时,HTML解析器会暂停工作,将JavaScript的解析交给JavaScript引擎处理,直到JavaScript被解析完毕并执行后,HTML解析器再继续工作。这里HTML解析器暂停工作的原因是因为,JavaScript可能会修改当前已经生成的DOM结构

对于引入的JavaScript文件。执行到script同样会暂停HTML解析器的工作,而HTML解析器工作暂停就意味着DOM解析产生了阻塞。而通过文件引入的JavaScript,可能需要从网络上下载,造成卡顿。为了解决这个问题,针对性的产生了如下优化方式:

  • 对于浏览器:渲染引擎收到字节流后,会开启预解析线程,该线程仅处理HTML中从外部引入的JavaScript或CSS文件,并提前进行下载;
  • 对于开发者:可使用CDN加速JavaScript文件的下载速度,并压缩JavaScript的体积。当JavaScript中不存在修改DOM的逻辑时,可以异步加载JavaScript脚本。

参考资料

22 | DOM树:JavaScript是如何影响DOM树构建的? (geekbang.org)