HTML解析器通过状态机逐字符扫描字节流,用栈机制构建DOM节点:遇<div>触发开始标签状态、创建元素并压栈,同步收集属性,文本生成Text节点挂载到栈顶,遇</div>弹出完成构造;innerHTML依赖上下文且不执行脚本或加载资源,DOMParser更接近主解析器但不执行脚本、不解析外部资源、容错性不同。

HTML 被渲染引擎转化为节点,本质是「解析器按字节流顺序读取标签,边建栈边构造父子关系」,不是一次性加载完再处理,也不是靠正则或字符串分割——错用这些方式会导致嵌套错乱、自闭合标签漏处理、属性丢失等硬伤。
HTML 解析器如何把 <div> 变成 DOM 节点
浏览器内置的 HTML 解析器(如 WebKit 的 HTMLParser 或 Blink 的 HTMLDocumentParser)以状态机方式逐字符扫描输入流。遇到 <div> 时,并非简单创建一个对象,而是:
- 触发「开始标签状态」,根据标签名(
div)创建一个HTMLDivElement实例,压入元素栈 - 解析过程中同步收集属性:如
<div id="app" class="container">→element.id = "app",element.className = "container" - 后续文本内容(如
Hello)被识别为字符数据,生成Text节点,挂载到当前栈顶元素(即div)的childNodes中 - 遇到
</div>时,弹出栈顶div节点,表示该元素构造完成
这个栈机制天然保证嵌套层级正确。比如 <div><p>text</p></div> 中,p 节点一定在 div 的 childNodes 里,不会平级。
为什么不能用 innerHTML = str 模拟完整解析流程
innerHTML 看似能“解析 HTML 字符串”,但它依赖当前上下文环境,且不完全等价于主文档解析器行为:
立即学习“前端免费学习笔记(深入)”;
- 在
<body>内设置innerHTML,解析器会自动补全缺失的<html>、<head>,但若在<table>内赋值含<div>,可能被静默修正为<tbody><tr><td>...</td></tr></tbody> - 它不触发完整的 DOM 构建生命周期(如
document.createElement()不会调用自定义元素的constructor) - 外部资源(如
<script src="a.js">)不会执行,<link rel="stylesheet">也不会加载 CSSOM
所以服务端或非浏览器环境(如 Node.js)想模拟真实解析,得用 DOMParser:new DOMParser().parseFromString(htmlStr, "text/html"),它更接近浏览器主解析器逻辑。
DOMParser 和浏览器主解析器的关键差异
DOMParser 是标准 API,但和页面加载时的主 HTML 解析器有实际区别:
- 不执行脚本:
<script>alert(1)</script>在DOMParser结果中只是个HTMLScriptElement节点,不会运行 - 不解析外部资源:不会发起
<img src>或<link href>的网络请求 - 错误容错策略不同:主解析器对不规范 HTML(如未闭合标签、错位
<table>)有复杂修复逻辑(如插入隐式<tbody>),DOMParser更严格,某些情况下直接报错或产出意外结构 - 编码处理差异:主解析器会从 HTTP header 或
<meta charset>推断编码;DOMParser默认按 UTF-8 解析,若传入 GBK 编码字符串且未转码,中文会变乱码
真正关键的不是“怎么生成节点”,而是理解「解析发生在字节流层面,栈驱动,与渲染树、CSSOM、JS 执行解耦但又在特定时机协同」——比如 <script> 遇到会阻塞 HTML 解析,但只影响 DOM 树构建,不影响已下载的 CSS 文件继续构建 CSSOM。



















