链接里套链接
<a> 的 content model 是 transparent(透明),但附带一条硬约束:不得有 interactive content 后代、不得有 a element 后代、不得有带 tabindex 的后代。关键词是**后代(descendant)**而非子元素——只要外层
<a> 的整棵子树里冒出另一个 <a>,无论中间隔了几层、隔着什么,都违规。下面给内外两个 <a> 之间夹一层不同的元素,看解析器分别怎么处理:
普通元素(span / div)夹在中间:嵌套被拆开。 <a> 是 formatting element,会进入 active formatting elements 列表。解析到内层 <a> 时,算法发现「列表里到最近 marker 为止存在 a」,于是触发
adoption agency 算法,先把外层 <a> 隐式闭合——两个 <a> 在 DOM 里落成平级,源码的嵌套意图并未实现。
<object> 夹在中间:嵌套被保留。 <object>(连同 <applet> / <marquee> / <template> / <td> 等)在起始时会往 active formatting elements 列表里插入一个 marker。于是解析内层 <a> 时,「到最近 marker 为止」这段里看不到外层 <a>(它在 marker 的另一侧),不触发 adoption agency,外层 <a> 不被闭合。DOM 里于是真实出现了 a 套 a——能渲染,但 non-conforming。
1 · 自由输入:贴任意 HTML,看它解析成什么
下面的输入直接传入 new DOMParser().parseFromString(html, "text/html"),渲染其 body 子树。标红的节点 = 这个 <a> 的祖先链里还有另一个 <a>(即 a 套 a 的违规)。
能解析 ≠ 合法。 解析器的宽容只保证「任何输入都有确定的 DOM」,它永远不报错;conformance 是另一套规则,由 Nu Html Checker 之类的 validator 按 content model 检查。上面 <object> 那个 case,DOM 里确实有
a 套 a,validator 必报 The element a must not appear as a descendant of the a element;而被拆平的 case,解析器替你做了错误恢复,但源码本身仍是非良构的写法,不应依赖这种「被动纠正」的行为。
正确写法:让两个链接并列,而不是嵌套。 HTML 无法表达「链接里再套一个指向别处的链接」——若语义上确有此需要(如「整句可点,其中某词另跳一处」),应改用其他交互方式(点击时用脚本拦截跳转),或重新设计信息结构。需要在链接子树里保留独立内容时,<template> 的内容进入独立的 fragment document,其内部即便有 <a> 也不算外层 <a> 的后代,因而合规。
相关链接
-
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 must not appear as a descendant of the a element」—— 与解析器的实际行为形成对照。 -
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 解析算法,与页面真实加载时一致 —— 所以这里看到的 DOM 树就是真机行为。