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

HTML 被渲染引擎转化为节点,本质是「解析器按字节流顺序读取标签,边建栈边构造父子关系」,不是一次性加载完再处理,也不是靠正则或字符串分割——错用这些方式会导致嵌套错乱、自闭合标签漏处理、属性丢失等硬伤。
HTML 解析器如何把 <div> 变成 DOM 节点
<p>浏览器内置的 HTML 解析器(如 WebKit 的 <code>HTMLParser 或 Blink 的 HTMLDocumentParser)以状态机方式逐字符扫描输入流。遇到 <div> 时,并非简单创建一个对象,而是:
<ul>
<li>触发「开始标签状态」,根据标签名(<code>div)创建一个 HTMLDivElement 实例,压入元素栈
解析过程中同步收集属性:如 <div id="app" class="container"> → <code>element.id = "app",element.className = "container"
后续文本内容(如 Hello)被识别为字符数据,生成 Text 节点,挂载到当前栈顶元素(即 div)的 childNodes 中
遇到
时,弹出栈顶
div 节点,表示该元素构造完成
这个栈机制天然保证嵌套层级正确。比如 <div><p>text</p></div> 中,p 节点一定在 div 的 childNodes 里,不会平级。
为什么不能用 innerHTML = str 模拟完整解析流程
innerHTML 看似能“解析 HTML 字符串”,但它依赖当前上下文环境,且不完全等价于主文档解析器行为:
Html2md
使用 Readability + Turndown 将 HTML 页面转换为干净的、Agent 友好的 Markdown。去除导航栏、广告、页脚、Cookie 横幅、社交按钮等干扰元素。支持 URL...
下载
- 在
内设置 innerHTML,解析器会自动补全缺失的 、,但若在 <table> 内赋值含 <code><div>,可能被静默修正为 <code><tbody><tr><td>...</td></tr></tbody>
- 它不触发完整的 DOM 构建生命周期(如
document.createElement() 不会调用自定义元素的 constructor)
- 外部资源(如
<script src="a.js"></script>)不会执行,<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>)有复杂修复逻辑(如插入隐式 <code><tbody>),<code>DOMParser 更严格,某些情况下直接报错或产出意外结构
- 编码处理差异:主解析器会从 HTTP header 或
<meta charset> 推断编码;DOMParser 默认按 UTF-8 解析,若传入 GBK 编码字符串且未转码,中文会变乱码
真正关键的不是“怎么生成节点”,而是理解「解析发生在字节流层面,栈驱动,与渲染树、CSSOM、JS 执行解耦但又在特定时机协同」——比如 <script></script> 遇到会阻塞 HTML 解析,但只影响 DOM 树构建,不影响已下载的 CSS 文件继续构建 CSSOM。