html解析器是边接收字节流边构建dom树的实时状态机,包含词法分析(tokenizer)和语法分析(tree construction)两个严格串行又高度耦合的阶段,依据w3c标准将原始字节流转换为内存中容错的dom树。

HTML解析器不是“先读完再处理”的黑盒,而是边接收字节流边构建DOM树的实时状态机。它不依赖完整文档,也不保证语法正确——遇到错位标签、缺失闭合、大小写混用,照样产出可用DOM。
HTML解析器到底在做什么
它把原始字节流(比如从网络收到的html响应体)按W3C HTML标准逐步转换成内存中的树形结构(即DOM),这个过程包含两个不可分割的阶段:词法分析(Tokenizer)和语法分析(Tree Construction)。两者严格串行但高度耦合,Tokenizer输出的每个token(如startTag、endTag、character)会立刻被Tree Construction消费并驱动栈操作。
关键点在于:DOCTYPE位置决定解析模式(标准模式 vs 怪异模式),而<meta charset="utf-8">必须出现在最前面——否则Tokenizer会用默认编码(如ISO-8859-1)解码后续内容,导致中文乱码或标签识别失败。
词法分析阶段常见陷阱
Tokenizer不是简单按切分字符串,而是用状态机逐字符推进。一旦状态判断出错,整个后续解析都会偏移。典型问题包括:
-
<divstyle></divstyle>(无空格)会被识别为一个非法标签名divstyle,而非div加属性 -
中间有空格 → 触发怪异模式 →document.body可能为null,JS查询失败 -
<script>console.log(</script>中未转义的会被当作新标签起点,造成提前截断 - UTF-8 BOM未被跳过时,首个
token可能带不可见字符,影响识别
语法分析阶段如何容错建树
Tree Construction用栈管理嵌套关系,但不校验合法性。它按HTML规范8.2.5.4定义的“插入模式”和“结束标签处理算法”自动修复常见错误:
<p>Hello</p><div class="aritcle_card flexRow artxards"> <div class="artcardd flexRow"> <a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill6712" title="Wechat HTML Publisher"><img src="https://img.php.cn/upload/skill/000/000/081/179109368394970.jpg" alt="Wechat HTML Publisher" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a> <div class="aritcle_card_info flexColumn"> <a rel="nofollow" href="/xiazai/skill6712" title="Wechat HTML Publisher" class="overflowclass">Wechat HTML Publisher</a> <p class="overflowclass">直接上传HTML富文本到微信公众号草稿箱。支持完整的HTML格式,无需Markdown转换。</p> </div> <a rel="nofollow" href="/xiazai/skill6712" title="Wechat HTML Publisher" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a> </div> </div> <div>World → 自动闭合<code><p></p>,再把<div>挂到<code>body下(不是p的子节点)-
<table><tr><td>A</td></tr></table>→ 自动补全<tbody>,并在<code>时终止<tr>和<code><td> <li> <code><div><p>Text</p></div>→ 先建div入栈,再建p作为其子节点,遇到时弹出div,p随之脱离 - 只扫描
和开头部分,遇到<script></script>就暂停(除非含async或defer) - 无法识别
srcset、data-src、style="background:url()"等非标准资源引用 - 不会等待CSS下载完成就继续解析HTML,但渲染必须等CSSOM就绪——所以把
<link>放末尾,会导致预加载延迟,首屏渲染卡顿
这种修复让页面能“看起来正常”,但也意味着CSS选择器可能失效(比如div > p匹配不到被移到body下的p),JS通过querySelector拿到的结构与源码不一致。
预加载扫描器(Preload Scanner)如何干扰主解析
它在Tokenizer运行的同时并行工作,只扫描未解析的HTML片段,提前发起<link rel="stylesheet">、<script src></script>、<img src>等资源请求。但它有严重限制:
真正容易被忽略的是:预加载扫描器看到的HTML,是Tokenizer尚未修正的原始token流。如果Tokenizer因编码错误把某个<link>识别成了文本节点,预加载器就完全看不到它——资源加载直接丢失,且无任何报错提示。










