本站所有源码均为自动秒发货,默认(百度网盘)
深入理解HTML解析与DOM构建过程
在前端开发中,我们每天都在和HTML打交道,但你是否真正了解浏览器是如何把一串普通的HTML文本,转化为我们能交互的网页的?今天就让我们一起深入探索HTML解析与DOM构建的全过程,从底层原理出发,搞清楚这一核心机制。
一、HTML解析的前置准备:字节到字符的转换
当我们在浏览器地址栏输入一个URL并按下回车后,浏览器首先会向服务器发起请求,获取到的响应结果其实是一串二进制字节流。这一步是所有解析工作的起点,浏览器会先完成以下转换:
- 字节解码:浏览器根据响应头中的
Content-Type字段(如text/html; charset=utf-8),或者HTML文件中的<meta charset="utf-8">标签,将二进制字节流解码为对应的字符。 - 字符规范化:统一不同编码格式的字符表示,确保后续解析的一致性。
二、HTML解析器的工作流程:从字符到DOM树
HTML解析器的核心任务是将字符流转换为DOM树,这是一个渐进式的过程,浏览器会一边下载HTML内容,一边进行解析,不会等到所有内容都下载完成才开始工作。
🧩 分词器:将字符拆分为Token
分词器是HTML解析的第一步,它会把输入的字符流拆分成一个个具有语义的Token,常见的Token类型包括:
- 起始标签:如
<html>、<div> - 结束标签:如
</html>、</div> - 自闭合标签:如
<img />、<input /> - 文本内容:如标签之间的文字
- 注释:如
<!-- 这是注释 -->
分词器会根据HTML5规范中的状态机来识别不同的Token,比如当遇到<字符时,就进入标签识别状态,当遇到>字符时,就表示一个标签Token结束。
🌳 构建器:将Token组装为DOM树
分词器生成的Token会被传递给构建器,构建器会根据Token的类型,逐步构建DOM树:
- 创建DOM节点:当遇到起始标签Token时,构建器会创建一个对应的DOM节点,并将其添加到DOM树中。
- 维护节点关系:构建器会维护一个栈结构,用来记录当前节点的层级关系。当遇到起始标签时,将对应的节点压入栈中;当遇到结束标签时,将对应的节点从栈中弹出,此时后续创建的节点就会成为弹出节点的父节点的子节点。
- 处理文本节点:当遇到文本Token时,构建器会创建一个文本节点,并将其添加到当前栈顶节点的子节点列表中。
举个简单的例子,对于以下HTML代码:
<div>
<p>Hello World</p>
</div>构建器的工作流程如下:
- 遇到
<div>起始标签,创建div节点,压入栈中,此时DOM树的根节点是div。 - 遇到
<p>起始标签,创建p节点,压入栈中,此时p节点成为div节点的子节点。 - 遇到文本Token”Hello World”,创建文本节点,添加到
p节点的子节点列表中。 - 遇到
</p>结束标签,将p节点从栈中弹出,此时栈顶节点回到div。 - 遇到
</div>结束标签,将div节点从栈中弹出,DOM树构建完成。
三、HTML解析中的特殊情况:脚本与样式的影响
在HTML解析过程中,脚本和样式的处理比较特殊,它们会影响解析的流程。
📜 脚本的阻塞特性
默认情况下,当HTML解析器遇到<script>标签时,会立即暂停解析过程,先执行脚本内容,然后再继续解析。这是因为脚本可能会修改DOM结构,比如使用document.write()方法,所以浏览器需要确保脚本执行时,前面的DOM已经构建完成。
不过,我们可以通过以下方式来优化脚本的加载和执行:
- 异步加载:使用
async属性,脚本会在后台下载,下载完成后立即执行,不会阻塞HTML解析,但执行顺序不确定。 - 延迟加载:使用
defer属性,脚本会在后台下载,等到HTML解析完成后再执行,执行顺序与脚本在HTML中的顺序一致。
🎨 样式的加载与解析
CSS样式的加载不会阻塞HTML解析,但会阻塞DOM的渲染。这是因为浏览器需要计算元素的样式,才能确定元素的最终布局和外观,如果样式还没有加载完成,浏览器会先渲染一个空白页面,等到样式加载完成后再重新渲染。
四、DOM构建完成后的后续工作
当DOM树构建完成后,浏览器还会进行以下工作:
- 构建CSSOM树:浏览器会解析所有的CSS样式,构建CSSOM(CSS对象模型)树。
- 生成渲染树:将DOM树和CSSOM树结合,生成渲染树,渲染树只包含需要显示的元素。
- 布局(Layout):计算每个元素在页面中的位置和大小。
- 绘制(Paint):将渲染树中的元素绘制到屏幕上。
- 合成(Composite):将绘制好的图层合成到一起,形成最终的页面。
五、深入理解的意义:优化前端性能
了解HTML解析与DOM构建的过程,对于我们优化前端性能有着重要的意义:
- 减少阻塞:合理使用
async和defer属性,避免脚本阻塞HTML解析。 - 优化加载顺序:将CSS样式表放在
<head>标签中,确保样式尽早加载,减少页面的白屏时间。 - 避免重排重绘:了解DOM树的构建过程,避免在脚本中频繁修改DOM结构,减少浏览器的重排和重绘。