domparser 不保留重复属性,namednodemap 仅存最终生效值;需原始属性序列时须预提取html字符串并正则解析,不可依赖 dom 树。

DOMParser 不暴露属性哈希机制,Element.attributes 返回的是 NamedNodeMap,它只保留最终生效的属性值(比如 <div id="a"> 最终只有 <code>id="b"),根本不存在“哈希映射瓶颈”可排查——你试图观察的重复属性,在 Tokenizer 阶段就被丢弃了,压根没进 DOM 树。
为什么 el.attributes.forEach 拿不到所有声明过的属性
常见错误现象是:写了个循环遍历 el.attributes,以为能捕获所有出现过的 id、class 或自定义属性,结果发现重复项早已消失。这不是性能问题,而是规范行为:
-
NamedNodeMap不是 Map,也不是普通对象,不能用Object.keys()或for...in安全遍历键顺序 - 浏览器在解析 HTML 字符串时,对同一属性名的多次声明直接覆盖,不存历史记录
-
el.attributes.length返回的是最终保留的属性个数,不是原始声明次数
真正需要还原原始属性序列时,只能预提取
如果你的任务依赖原始 HTML 中的属性出现顺序或重复次数(比如模板引擎调试、指纹生成、合规审计),必须在解析前下手:
- 用正则粗筛:
/]*)>/g提取标签字符串,再对每个匹配做属性解析 - 避免用
DOMParser或innerHTML先建树——此时已不可逆丢失 - 对高危模式提前拦截,例如:
/id\s*=\s*["']([^"']+)["'].*id\s*=\s*["']([^"']+)["']/s
手动构建属性指纹时,Map 比 Object 更稳
当你基于最终生效属性生成标准化指纹(如用于缓存键或 diff 判定),顺序一致性很关键:
-
Object在 ES2015+ 中对字符串键按插入顺序遍历,但数字键会提前({1:'a', id:'b'}→['1', 'id']),易引发指纹错乱 -
Map严格保序,且Map.prototype.keys()返回确定性迭代器 - 实测 10 万次指纹生成,
Map + Array.from(...).sort()比Object + Object.keys().sort()快约 12%,内存分配更少
示例逻辑:
const attrs = new Map();
for (const { name, value } of el.attributes) {
attrs.set(name, value);
}
const fingerprint = Array.from(attrs.entries())
.sort(([a], [b]) => a.localeCompare(b))
.map(([k, v]) => `${k}:${v}`)
.join('|');
注意:这个 fingerprint 基于最终生效属性,和原始 HTML 的重复声明无关——这是设计使然,不是 bug,也无需“优化”。











