dom树的根节点是document(nodetype===9),而非标签;childnodes包含所有节点类型,children仅含元素节点;innerhtml会触发解析并丢失事件,textcontent则安全高效。

DOM 树不是“HTML 标签的简单复制”,而是浏览器解析 HTML 后生成的、带类型和关系的内存结构;不理解这点,document.body.parentNode 返回 html 而不是 document 就会让人困惑。
document 才是真正的根节点,不是 html
很多人以为 document.documentElement(即 )是 DOM 树起点,其实 document 才是根——它不对应任何标签,nodeType === 9,而 是它的第一个子元素(nodeType === 1)。这意味着:
-
document.childNodes可能开头就有文本节点(比如 DOCTYPE 前的换行或空格),遍历时容易漏判或报错 -
document.body和document.head是快捷属性,实际路径是document.documentElement.children[0]或[1],不是document的直接子节点 - 用
for...of遍历document.childNodes时,得先检查node.nodeType === 1才能安全取node.tagName,否则对文本节点调用会出错
childNodes 和 children 的差异在真实项目里会放大成 bug
这两个 API 看似只差一个字母,但行为完全不同:
-
childNodes返回所有子节点:元素、文本、注释(nodeType === 8)、甚至 IE 下的 CDATA -
children只返回nodeType === 1的元素节点,是实时的HTMLCollection - 判断容器是否为空子元素?用
element.children.length === 0,别用element.childNodes.length——后者可能因空白文本存在而返回非零值 - 想过滤掉空格文本节点?别写
Array.from(el.childNodes).filter(n => n.nodeType === 1),直接用el.children更快更稳
为什么 getElementById 快,但你多数时候该用 querySelector
getElementById 内部走哈希索引,确实比 querySelector 快;但它的前提是 ID 全局唯一且稳定——现实中常被框架、SSR、动态插入破坏:
- ID 重复时,
getElementById只返回第一个匹配项,不报错也不提醒 - 组件化场景下,局部作用域查找(如
container.querySelector('.item'))比全局 ID 更可靠 - 需要向上找父级?
element.closest('.modal')比反复查parentNode更简洁,且能跨层级 - 若必须用 ID,建议配合
document.getElementById(id)?.ownerDocument === document做归属校验,防跨 iframe 误读
innerHTML 和 textContent 改内容,副作用完全不在一个量级
表面都是“改文字”,但底层机制差着一个解析器:
-
innerHTML = '<span>hello</span>'会触发 HTML 解析、节点重建、原有事件监听器全部丢失 -
textContent = '<span>hello</span>'只改文本节点值,不触发解析,绑定的事件毫发无损,还能天然防 XSS - 批量插入带结构的内容?优先用
document.createElement+appendChild,而非拼接字符串再赋给innerHTML - 清空容器?
el.textContent = ''比el.innerHTML = ''更轻量,尤其当容器内有大量绑定事件时
DOM 树的真实复杂度藏在节点类型和父子关系里,而不是嵌套深度;nodeType 和 parentNode 这类基础属性,比花哨的选择器更能帮你避开边界问题。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











