Skip to main content

DOM Node 基础概念

· 4 min read

DOM 里 Node 是基类,Element 只是 7 种节点之一——文本、注释、文档都是 Node 但 不是 Element

  • 节点类型:日常打交道最多是 Element / Text / Comment / Document 四种
  • 三大属性:nodeNamenodeValuetextContent 各管一摊
  • nodeType:用 Node.ELEMENT_NODE 常量,禁硬编码数字
  • 父子关系:parentElement 只对 Element 父节点有值
  • NodeList:实时的 集合,遍历时增删子节点会变化

七种节点类型

DOM 规范定义了 7 种节点,全部继承自 Node 接口:

类型nodeType含义典型来源
Document9整个文档根document
DocumentType10<!DOCTYPE> 声明document.doctype
Element1HTML 标签document.querySelector(...)
Attr2元素属性(已废弃)
Text3标签之间的文本.firstChild
Comment8<!-- --> 注释模板里的批注
DocumentFragment11文档片段document.createDocumentFragment()

提示Attr 节点在 DOM Level 4 已被标记为废弃,不要再用 .getAttributeNode()。属性读写走 getAttribute / setAttribute / element.attributes

日常 90% 的代码只跟 Element、Text、Comment、Document 四种打交道,其他三种基本只在底层 API 里出现。


三大节点属性

nodeNamenodeValuetextContent 三者功能不重叠:

  • nodeName:只读,返回节点名称(元素是大写标签名、文本是 #text、文档是 #document
  • nodeValue:可读写,只对 Text 和 Comment 节点有效,其他类型一律 null
  • textContent:可读写,返回当前节点和所有后代的文本拼接,自动忽略 HTML 标签
// <div id="d1">hello <span>world</span></div>
const div = document.getElementById('d1');

div.nodeName; // "DIV"
div.nodeValue; // null(元素节点)
div.firstChild.nodeValue; // "hello "
div.textContent; // "hello world"

最佳实践:用户输入的内容永远用 textContent 赋值,不要用 innerHTML。前者自动转义 < >,XSS 无效。


nodeType:别硬编码数字

nodeType 返回节点对应的整数值。早期代码经常看到这种写法:

if (node.nodeType === 1) { ... }

数字 1 是哪类节点?没人记得住。正确的姿势是用 Node 上的常量

if (node.nodeType === Node.ELEMENT_NODE) { ... }

可读性天差地别,IDE 还能跳转。Node 上有一整套常量:DOCUMENT_NODEELEMENT_NODETEXT_NODECOMMENT_NODEDOCUMENT_FRAGMENT_NODEDOCUMENT_TYPE_NODEATTRIBUTE_NODE(已废弃,但仍存在)。


parentNode vs parentElement

父节点只可能是三种类型:Element、Document、DocumentFragment。但 parentElement 这个属性只对 Element 父节点有值——遇到 Document 根节点会返回 null

document.documentElement.parentNode; // document
document.documentElement.parentElement; // null

绝大多数场景里 parentElement 更有用:往上爬通常是想找 Element 父节点继续操作。parentNode 是历史遗留,只在需要兼容 Document 父节点的场景才用


NodeList 是实时的(踩坑重灾区)

childNodes 返回的 NodeList实时集合(live collection)——文档结构变化时它会自动更新:

const list = parent.childNodes;
list.length; // 假设是 5

parent.appendChild(document.createElement('div'));
list.length; // 现在变成 6

更坑的是迭代过程中增删子节点,循环会跳过或重复元素。

最佳实践:永远把 NodeList 转成静态数组再处理:

[...parent.childNodes].forEach(node => { ... });
Array.from(parent.childNodes).forEach(node => { ... });

querySelectorAll 返回的 NodeList 是静态的,不会随 DOM 变化更新——这条规则只对 childNodes 生效。

集合是否实时来源
NodeList (live)childNodes
NodeList (static)querySelectorAll
HTMLCollectionchildrengetElementsByTagName

注意:HTMLCollection 也是实时的,而且没有 forEach——遍历只能用 for 循环或转数组。


References

  1. Node - MDN —— MDN 完整参考
  2. NodeList - MDN —— NodeList 实时性详解
  3. 阮一峰:DOM 模型概述 —— 经典中文教程