a 元素的嵌套约束
<a> 的 content model 是 transparent(透明),但附带一条硬约束:不得有 interactive content 后代、不得有 a element 后代、不得有带 tabindex 的后代。关键词是后代 (descendant) 而非子元素——外层
<a> 的整棵子树里只要冒出另一个 HTML 命名空间的 <a>,隔几层都违规。两个例外在 §3 交代:<template> 的内容不算后代,SVG 命名空间的 <a> 也不在这条约束的辖域内。
1 · 夹层元素决定的三种结果
内外两个 <a> 之间夹一层不同元素,Chrome 151 用 DOMParser 解析的结果分三档(核对于 2026-08):不夹或夹 <span> 时 DOM 里是两个平级的 <a>;夹 <div> 时是三个 <a>;夹
<object> 时嵌套原样保留。
警示 · 夹 <span> 与夹 <div> 结果并不相同。<a> 是 formatting element,会进入 active formatting elements 列表;解析到内层 <a> 时算法发现「列表里到最近 marker 为止存在 a」,于是触发
adoption agency 算法。<span> 不在 special 元素列表里,算法走「无 furthest block」分支:外层 <a> 连同 <span> 一起从开栈弹出并移出列表,<span> 作为空元素留在 DOM 里,内层 <a> 落到
<body> 上——两个 <a> 平级。<div> 是 special 元素,算法走 furthest block 分支:它会克隆外层 <a>、把 <div> 的子节点搬进克隆再挂回去。实测夹 <div> 时 DOM 里有三个
<a>,其中一个是 <div> 里的空克隆,两个 href="#outer" 也不平级。
警示 · <object> 夹在中间时嵌套被保留。<object>(连同 <applet> / <marquee> / <template>,以及处于表格上下文中的 <td> / <th> /
<caption>)在起始时会往 active formatting elements 列表里插入一个 marker。于是解析内层 <a> 时,「到最近 marker 为止」这段里看不到外层 <a>,不触发 adoption agency,外层 <a> 也不被弹出。DOM 里于是真实出现了
a 套 a——能渲染,但 non-conforming。注意裸写的 <td> 在 in body 模式下会被直接忽略,拿它当夹层反而得到被拆平的结果。
2 · 自由输入:任意标记的解析结果
下面的输入传入 new DOMParser().parseFromString(html, "text/html") 并渲染其 body 子树。标红的节点表示这个 <a> 的祖先链里还有另一个 <a>。
注 · 能解析不等于合规。解析算法的宽容保证的是「任何字符流都得到一棵确定的 DOM、解析过程不中止」,而不是「不产生错误」——规范里 parse error 是正式定义的产物,近百种命名错误各有其名,只是不致命,conformance checker 必须报告它们。上面 <object> 那一档 DOM
里确实有 a 套 a,Nu Html Checker 会报 The element “a” with the attribute “href” must not appear as a descendant of the “a” element.;被拆平的那几档则是解析器做了错误恢复,源码本身仍是 non-conforming
的写法,不应依赖这种被动纠正。
警示 · 本页的解析走 DOMParser,与页面加载用的是同一套解析算法,但它创建的 Document 没有浏览上下文、scripting flag 为 disabled,少数分支因此与真机不同。最典型的是 <noscript>:实测同一段
<noscript><a href="#x">y</a></noscript>,DOMParser 解出 1 个 <a> 元素,而真实页面的 innerHTML 把内容当作 RAWTEXT、<a> 只是文本,querySelectorAll('a') 为 0(核对于 2026-08)。
3 · 两个不算后代的例外
<template> 的内容进入它的 content——一个由独立惰性 Document 拥有的 DocumentFragment,不在外层 <a> 的后代链上,因此 <a> 里套一个 <template>、其中再放 <a> 是合规的(Nu
实测零报错)。代价是 template 内容不渲染,解决不了「整句可点、其中某词另跳」的原始需求,它更多是后代判定的一处边界而非可用方案。
SVG 命名空间的 <a> 是另一个例外:content model 里说的 a element 指 HTML 命名空间的那一个,SVG 的 <a> 既不是它、也不属于 HTML 的 interactive content,因此
<a href="#o">L <svg><a href="#i">s</a></svg></a> 在 Nu 下同样零报错。要留意的是 querySelectorAll('a') 这类类型选择器在 HTML 文档里不区分命名空间,实测会把 SVG 的
<a> 一并选中——按命名空间判定违规时得显式比对 namespaceURI。
4 · HTML 无法表达的那件事
HTML 没有「链接里再套一个指向别处的链接」这种结构。语义上确有此需要时,只能改用其他交互方式(如点击时用脚本拦截跳转分流),或重新设计信息结构:把两处跳转拆成并列的两个链接。
相关链接
-
HTML Standard · Adoption agency algorithm
WHATWG
对应本页「拆开」一档:解析器遇到错误嵌套的 formatting element(如
a/b/i)时,如何重整标签栈:把外层标签弹出开栈并移出列表,必要时克隆它。active formatting elements 列表与 marker 的定义也在 parsing 章。 -
HTML Standard · The a element
WHATWG
对应本页 content model:
a是 transparent,但「no interactive content descendant, noaelement descendant, no descendant with tabindex」这条后代级约束的原文出处。 -
Nu Html Checker
validator.w3.org
W3C 官方 conformance 检查器。把本页
<object>那个 case 输入其中,会得到「The element “a” with the attribute “href” must not appear as a descendant of the “a” element.」(该预设的 object 缺 data 属性, 另有一条无关报错)—— 与解析器的实际行为形成对照。 -
MDN · <a>: The Anchor element
developer.mozilla.org
逐项说明
<a>的技术约束(Technical summary 里的 Permitted content / Implicit ARIA role),以及「<a>不可嵌套、不可包含交互内容」的可读版解释。 -
MDN · DOMParser
developer.mozilla.org
本页解析所用接口:
parseFromString(str, "text/html")走的就是浏览器标准 HTML 解析算法, 与页面加载同源; 差别在于它创建的 Document 没有浏览上下文、scripting flag 为 disabled,<noscript>等少数分支的结果与真机不同。