深入解析JavaScript对DOM树构建的影响机制

作者:白露

时间:2026-10-24

浏览:632

本文详细解析了浏览器中DOM树的构建过程,重点阐述了JavaScript脚本如何阻塞HTML解析。当遇到script标签时,解析器会暂停并执行脚本,若涉及CSSOM则需先解析样式。文章还介绍了Chrome浏览器的预解析优化及async、defer等异步加载策略,帮助开发者理解并优化页面渲染性能。

文章目录

文档对象模型 (DOM)

文档对象模型(DOM)作为Web页面与脚本或编程语言之间的桥梁,将网页内容映射为一棵逻辑树。这棵树的每个分支末端都是一个节点(node),每个节点都包含相应的对象(objects)。通过DOM提供的方法,开发者可以以编程方式访问这棵树,进而修改文档的结构、样式和内容。此外,节点还可以关联事件处理器,当事件触发时,这些处理器会被执行。

  • 从页面生成的角度来看,DOM是构建页面的基础数据结构。
  • 从JavaScript脚本的角度来看,DOM提供了一套操作接口。通过这套接口,JavaScript可以访问DOM结构,从而动态改变文档的结构、样式和内容。
  • 从安全角度来看,DOM充当了一道安全防线。在DOM解析阶段,一些不安全的内容会被直接过滤掉,从而被拒之门外。

简而言之,DOM是HTML的内部数据结构表示,它将Web页面与JavaScript脚本连接起来,并负责过滤掉不安全的内容。

DOM 和 JavaScript

DOM本身并非编程语言,但如果没有DOM,JavaScript语言将无法处理任何网页、XML页面或涉及元素的概念与模型。文档中的每个元素——包括整个文档、文档头部、表格、表头及表格内的文本——都是DOM的一部分。因此,它们可以通过DOM和诸如JavaScript这样的脚本语言进行访问和处理。

起初,JavaScript与DOM紧密交织,但随后演变为两个独立的实体。JavaScript可以访问并操作存储在DOM中的内容,因此可以近似地认为:

API (web 或 XML 页面) = DOM + JS (脚本语言)

DOM 和 JavaScript

DOM 树如何生成

在渲染引擎内部,存在一个名为HTML解析器(HTMLParser)的模块,其职责是将HTML字节流转换为DOM结构。HTML解析器并非等待整个文档加载完毕后再进行解析,而是采用边加载边解析的策略。网络进程加载了多少数据,HTML解析器就解析多少数据。

具体流程如下:网络进程接收到响应头后,会根据响应头中的content-type字段判断文件类型。例如,若content-type值为“text/html”,浏览器会判定这是一个HTML文件,并据此选择相应的解析引擎,进而为该请求选择或创建一个渲染进程。当渲染进程准备就绪后,网络进程与渲染进程之间会建立一条共享数据的管道。网络进程将接收到的数据放入管道,而渲染进程则从管道另一端不断读取数据,并同步将其发送给HTML解析器。

可以将这条管道想象成一根“水管”。网络进程接收到的字节流如同水流注入这根“水管”,而“水管”的另一端连接着渲染进程的HTML解析器。解析器动态接收字节流,并将其解析为DOM结构。

文档对象模型 (DOM)

文档对象模型 (DOM)

从图中可以看出,字节流转换为DOM需要经历三个阶段。

解析 HTML 的三个阶段

第一阶段,通过分词器将字节流转换为Token。

HTML解析过程同样需要借助分词器,先将字节流转换为一个个Token,这些Token分为Tag Token和文本Token。通过词法分析生成的HTML代码Token如下图所示:

文档对象模型 (DOM)

由图可知,Tag Token进一步细分为StartTag和EndTag。

第二阶段是将Token解析为DOM节点。

HTML解析器维护了一个Token栈结构,主要用于计算节点之间的父子关系。在第一阶段生成的Token会按顺序压入该栈中。具体的处理规则如下:

  • 如果压入栈中的是StartTag Token,HTML解析器会为该Token创建一个DOM节点,并将该节点添加到DOM树中。其父节点即为栈中相邻元素所生成的节点。
  • 如果分词器解析出的是文本Token,则会生成一个文本节点,并将其添加到DOM树中。文本Token不需要压入栈中,其父节点即为当前栈顶Token所对应的DOM节点。
  • 如果分词器解析出的是EndTag标签(例如EndTag div),HTML解析器会检查Token栈顶的元素是否为StartTag div。如果是,则将StartTag div从栈中弹出,表示该div元素解析完成。

通过分词器产生的新Token不断进行压栈和出栈操作,整个解析过程持续进行,直到分词器完成所有字节流的转换。

第三阶段是将DOM节点添加到DOM树中。

将创建的DOM节点添加到document上,从而形成完整的DOM树。

详解 HTML 解析流程

HTML解析器启动时,会默认创建一个以document为根的空DOM结构,并将一个StartTag document的Token压入栈底。随后,分词器解析出的第一个StartTag html Token会被压入栈中,并创建一个html的DOM节点,添加到document上,如下图所示:

文档对象模型 (DOM)

接着按照相同的流程解析出StartTag body和StartTag div,此时Token栈和DOM的状态如下图所示:

文档对象模型 (DOM)

接下来解析出第一个div的文本Token,渲染引擎会为该Token创建一个文本节点,并将其添加到DOM中。该文本节点的父节点即为当前Token栈顶元素对应的节点,如下图所示:

文档对象模型 (DOM)

随后,分词器解析出第一个EndTag div。此时HTML解析器会判断当前栈顶元素是否为StartTag div。如果是,则从栈顶弹出StartTag div,如下图所示:

文档对象模型 (DOM)

按照相同的规则继续解析,最终结果如下图所示:

文档对象模型 (DOM)

通过上述介绍,相信您已经清楚了DOM的生成过程。然而,在实际生产环境中,HTML源文件通常包含CSS、JavaScript以及图片、音频、视频等资源,因此处理过程远比上述Demo复杂。不过,理解了这一简单的Demo生成过程,我们就可以进一步分析更复杂的场景。

JavaScript 是如何影响 DOM 生成的

如果页面中包含JavaScript脚本或引入了脚本文件,则脚本的解析过程与上述过程有所不同。在script标签之前,所有解析流程与之前介绍的一致。但当解析到script标签时,渲染引擎判定这是一段脚本,此时HTML解析器会暂停DOM的解析,转而由JavaScript引擎介入。这是因为JavaScript脚本可能需要修改当前已经生成的DOM结构。

如果脚本是通过JavaScript文件加载的,则需要先下载这段JavaScript代码。这里需要重点关注下载环境,因为JavaScript文件的下载过程会阻塞DOM解析,而下载过程通常非常耗时,受网络环境、JavaScript文件大小等因素影响。

如果脚本是直接内嵌的JavaScript脚本,则直接执行。

如果JavaScript脚本修改了DOM中div的内容,那么执行该脚本后,已解析过的div节点内容也会被修改。脚本执行完成后,HTML解析器恢复解析过程,继续解析后续内容,直至生成最终的DOM。

还有一种情况:如果JavaScript代码中包含修改页面CSS样式的语句,用于操纵CSSOM,那么在执行JavaScript之前,需要先解析JavaScript语句之上所有的CSS样式。因此,如果代码中引用了外部CSS文件,在执行JavaScript之前,还需要等待外部CSS文件下载完成,并解析生成CSSOM对象之后,才能执行JavaScript脚本。

而JavaScript引擎在解析JavaScript代码之前,无法得知JavaScript是否操纵了CSSOM。因此,渲染引擎在遇到JavaScript脚本时,无论该脚本是否操纵CSSOM,都会执行CSS文件下载和解析操作,然后再执行JavaScript脚本。所以说JavaScript脚本是依赖样式表的。

通过上述分析,我们得知JavaScript会阻塞DOM生成,而样式文件又会阻塞JavaScript的执行。因此,在实际工程中需要重点关注JavaScript文件和样式表文件,使用不当会影响页面性能。

解析过程中的优化

为防止页面阻塞,Chrome浏览器做了很多优化,其中一个主要的优化是预解析操作。当渲染引擎收到字节流之后,会开启一个预解析线程,用来分析HTML文件中包含的JavaScript、CSS等相关文件,解析到相关文件之后,预解析线程会提前下载这些文件。

再回到DOM解析上,我们知道引入JavaScript线程会阻塞DOM,不过也有一些相关的策略来规避,比如使用CDN来加速JavaScript文件的加载,压缩JavaScript文件的体积。另外,如果JavaScript文件中没有操作DOM相关代码,就可以将该JavaScript脚本设置为异步加载,通过async或defer来标记代码,使用方式如下所示:


<script async type="text/javascript" src='foo.js'></script>

<script defer type="text/javascript" src='foo.js'></script>

async 和 defer 虽然都是异步的,不过还有一些差异,使用 async 标志的脚本文件一旦加载完成,会立即执行;而使用了 defer 标记的脚本文件,需要在 DOMContentLoaded 事件之前执行。

总结

首先我们介绍了DOM是如何生成的,然后又基于DOM的生成过程分析了JavaScript是如何影响到DOM生成的。也谈到CSS和JavaScript都会影响到DOM的生成。

DOM生成的过程

解析HTML需要通过分词器先将字节流转换为Token。

如果压入到栈中的是StartTag Token,HTML解析器会为该Token创建一个DOM节点,然后将该节点加入到DOM树中。如果分词器解析出来是文本Token,那么会生成一个文本节点,然后将该节点加入到DOM树中。如果分词器解析出来的是EndTag标签,HTML解析器会查看栈顶的元素是否是StarTag div,如果是,就将StartTag div从栈中弹出,表示该div元素解析完成。

通过分词器产生的新Token就这样不停地压栈和出栈,整个解析过程就这样一直持续下去,直到分词器将所有字节流分词完成。

在解析过程中如果遇到JavaScript代码,则停止HTML解析,如果js通过脚本加载的则先下载该脚本再执行,再执行之前CSS也会被解析生成CSSOM。经此过程直至整个DOM构建完成。

到此这篇关于JavaScript是如何影响DOM树构建的文章就介绍到这了,更多相关JavaScript DOM树构建内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章