DOM Node 基础概念
DOM 里 Node 是基类,Element 只是 7 种节点之一——文本、注释、文档都是 Node 但 不是 Element。
- 节点类型:日常打交道最多是 Element / Text / Comment / Document 四种
- 三大属性:
nodeName、nodeValue、textContent各管一摊 - nodeType:用
Node.ELEMENT_NODE常量,禁硬编码数字 - 父子关系:
parentElement只对 Element 父节点有值 - NodeList:实时的 集合,遍历时增删子节点会变化
七种节点类型
DOM 规范定义了 7 种节点,全部继承自 Node 接口:
| 类型 | nodeType | 含义 | 典型来源 |
|---|---|---|---|
| Document | 9 | 整个文档根 | document |
| DocumentType | 10 | <!DOCTYPE> 声明 | document.doctype |
| Element | 1 | HTML 标签 | document.querySelector(...) |
| Attr | 2 | 元素属性(已废弃) | — |
| Text | 3 | 标签之间的文本 | .firstChild |
| Comment | 8 | <!-- --> 注释 | 模板里的批注 |
| DocumentFragment | 11 | 文档片段 | document.createDocumentFragment() |
提示:Attr 节点在 DOM Level 4 已被标记为废弃,不要再用 .getAttributeNode()。属性读写走 getAttribute / setAttribute / element.attributes。
日常 90% 的代码只跟 Element、Text、Comment、Document 四种打交道,其他三种基本只在底层 API 里出现。
三大节点属性
nodeName、nodeValue、textContent 三者功能不重叠:
nodeName:只读,返回节点名称(元素是大写标签名、文本是#text、文档是#document)nodeValue:可读写,只对 Text 和 Comment 节点有效,其他类型一律nulltextContent:可读写,返回当前节点和所有后代的文本拼接,自动忽略 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_NODE、ELEMENT_NODE、TEXT_NODE、COMMENT_NODE、DOCUMENT_FRAGMENT_NODE、DOCUMENT_TYPE_NODE、ATTRIBUTE_NODE(已废弃,但仍存在)。
parentNode vs parentElement
父节点只可能是三种类型:Element、Document、DocumentFragment。但 parentElement 这个属性只对 Element 父节点有值——遇到 Document 根节点会返回 null:
document.documentElement.parentNode; // document
document.documentElement.parentElement; // null
绝大多数场景里 parentElement 更有用:往上爬通常是想找 Element 父节点继续操作。parentNode 是历史遗留,只在需要兼容 Document 父节点的场景才用。