HTML 是构建网页结构的基础,而“解析 HTML”是前端开发、爬虫编程、浏览器内核等领域的核心技术之一。本文将带你从手动构建状态机开始,理解 HTML 是如何被逐字符拆解、识别和组织成 DOM 树的。
HTML 解析过程
标签和内容
- 首先,我们来一段最简单的 html 字符串
1 | const htmlString = `<div>hello world</div>`; |
- 接下来 拆分解析过程, 将上述 html 粗略拆分 得到以下结果
1 | < |
- 然后 我们根据每一步来定义一下状态
1 | < TAG_OPEN |
- 最后 来编写我们的 解析函数
1 | const TokenType = { |
输出结果为
1 | { |
属性
接下来我们加入属性解析的内容
1 | const htmlString = `<div attr="attrValue">hello world</div>`; |
首先 还是和上面一样, 我们来简单分析一下属性的状态
1 |
|
然后 根据上面的函数 ATTRIBUTE_NAME 是在 TAG_NAME 状态时可以被第一次探测到
所以 我们在 TAG_NAME 里面加一个 if 分支去处理 标签
1 | const TokenType = { |
输出结果
1 | { |
多个属性
1 | parseHtml(`<div attr="attrValue" test="123">hello world</div>`); |
输出
1 | { |
子元素
接下来我们来处理嵌套内容
1 | const htmlString = `<div attr="attrValue"> |
继续按照上面的分析对内容进行拆解
1 | 空白字符 DATA |
可以看出 这里的属性之前都包含了, 所以暂时我们不改变状态枚举
1 | const TokenType = { |
同时 我们发现 最外层的 div 后面接的下一个 endTag 是 h3
所以我们引入 stack 来处理标签结束, 并增加根节点和 children 属性来处理嵌套
接下来是分析过程
首先我们来分析标签的创建和出入栈
- 第一个 div 开始标签 创建标签 push 进 stack
- 第一个 h3 开始标签 创建标签 push 进 stack
- 第一个 h3 结束标签 取出 stack 的第一个标签
- 第二个 div 开始标签 创建标签 push 进 stack
- 第二个 div 结束标签 取出 stack 的第一个标签
- 第一个 div 结束标签 取出 stack 的第一个标签
下面是解析函数
1 | const TokenType = { |
输出结果
1 | { |
文本内容/自闭合标签
接下来我们来处理文本内容和自闭合标签
上面子元素的输出结果可以看出来, 每一个子元素的内容都多了一个\n ,并且如果我们在元素之间加上文本内容可能会出现解析错误
比如
1 | parseHtml(`<div attr="attrValue"> |
后面的 33333 会被解析到 h3 里面去
按照惯例, 我们继续来分析下状态
1 | 空白字符 DATA |
DATA 好像不够用怎么办 ?
接下来我们来引入 TEXT 节点 来处理文本内容, 并增加对自闭合标签的支持
下面是代码
1 | const TokenType = { |
输出结果为
1 | { |
style 解析
接下来来我们来对样式进行解析
html 内可能存在两种情况的样式
内敛样式 和 style 节点内的样式
通过上面的方法, 我们先尝试解析一下样式
1 | const htmlString = `<div class="div" style="width: 100px; height: 100px; background: #f80;">内容</div> |
我们会得到 2 个形式的 style
1 | // div上 |
接下来我们单独对每个node节点进行判断 来将style插入节点内
1 | // parseCss.js |
输出
1 |
|
总结
以上就是HTML解析的简要过程,通过构建状态机,我们逐步识别出标签、属性、文本节点等结构。
尽管目前的实现还较为简化,但已经初步展现了浏览器解析 HTML 的核心机制。
后续我们还可以在此基础上扩展更多复杂的规则和错误处理逻辑。