XML DOM 教程
XML DOM 教程
XML DOM(XML Document Object Model)是 W3C 定义的标准编程接口,用于访问和操作 XML 文档。通过 DOM,程序可以读取、修改、添加和删除 XML 中的节点,把 XML 当作一棵可编程的节点树来处理。
本教程内容
本教程从 XML DOM 的基本概念讲起,逐步覆盖:节点与节点树、解析 XML、DOM 方法、节点访问与导航、节点的增删改查、XMLHttpRequest,以及 Node、Element、Attribute、Text 等核心对象的详细说明。
为什么要学 XML DOM
- XML 仍是配置文件与数据交换的常用格式
- DOM 是理解所有文档型接口(包括 HTML DOM)的基础
- 掌握节点树模型,前端开发更扎实
- 很多后端与桌面技术依赖 XML 处理
适用语言
DOM 是语言无关的标准,本教程以 JavaScript 为例(浏览器原生支持),概念同样适用于 Python、Java、PHP 等语言。
学习前提
建议具备基础的 HTML/JavaScript 知识,了解 XML 基本语法即可开始。
DOM 简介
DOM 简介
文档对象模型(Document Object Model,DOM)是 W3C 的官方标准,定义了访问和操作 XML/HTML 文档的方法与属性。
DOM 是什么
DOM 把文档表示为一棵节点树:文档中的每个元素、属性、文本都是一个节点。通过 DOM 接口,程序可以:
- 遍历文档中的所有节点
- 读取节点内容(元素名、文本、属性)
- 修改节点(改文本、改属性)
- 创建、删除、替换、克隆节点
DOM 的层级
DOM 标准分层:
- 核心 DOM: 操作 XML/HTML 的基础接口
- XML DOM: 针对 XML 的接口(本教程)
- HTML DOM: 针对 HTML 的接口(更上层,包含
document.forms、document.images 等便捷属性)
节点树示例
<?xml version="1.0"?>
<bookstore>
<book>
<title>Ruby 入门</title>
<price>59.0</price>
</book>
</bookstore>
对应的节点树:
bookstore (根元素)
└── book (元素节点)
├── title (元素节点)
│ └── "Ruby 入门" (文本节点)
└── price (元素节点)
└── "59.0" (文本节点)
DOM 与编程语言
DOM 是接口标准,各语言提供实现:浏览器 JavaScript、Python 的 xml.dom/minidom、Java 的 javax.xml.parsers 等。接口概念一致,语法不同。
版本
DOM 1 级(1998):核心接口;DOM 2 级(2000):事件、遍历等;DOM 3 级(2004):加载保存、XPath 等。现代浏览器与解析器基本都支持。
DOM 节点
DOM 节点
节点(Node)是 DOM 的基本组成单元。理解节点的类型与关系,是操作 XML 的前提。
什么是节点
文档中的一切内容都是节点。XML 文档是节点的集合,节点按层次组织成树状结构。
节点类型
常见节点类型:
- 元素节点(element): <book>、<title>
- 属性节点(attribute): <book id="1"> 中的 id
- 文本节点(text): 元素中的文本内容
- 注释节点(comment): <!-- 注释 -->
- 处理指令(processing instruction): <?xml?> 等
- CDATA 节点: <![CDATA[...]]>
- 文档节点(document): 整个文档的根
每种节点对应一个 nodeType 常量:
- 元素: 1
- 属性: 2
- 文本: 3
- CDATA: 4
- 注释: 8
- 文档: 9
节点关系
<bookstore> (根节点)
├── <book id="1"> (bookstore 的子节点)
│ ├── <title>Ruby</title> (book 的子节点)
│ └── <price>59</price>
└── <book id="2"> (book 的兄弟节点)
- 父节点(parentNode)
- 子节点(childNodes)
- 兄弟节点(sibling)
- 后代节点(descendant)
- 祖先节点(ancestor)
节点的属性
每个节点都有:
- nodeName: 节点名称(元素=标签名,文本=#text)
- nodeValue: 节点值(元素=null,文本=文本内容,属性=属性值)
- nodeType: 节点类型(数字常量)
- parentNode / childNodes / firstChild / lastChild
- nextSibling / previousSibling
- attributes(NamedNodeMap,仅元素节点)
JavaScript 判断节点类型
if (node.nodeType === 1) {
console.log("元素节点: " + node.nodeName);
} else if (node.nodeType === 3) {
console.log("文本节点: " + node.nodeValue);
}
掌握节点类型与关系,后面章节的遍历、导航、增删改查都是在此基础上展开的。
DOM 节点树
DOM 节点树
DOM 把 XML 文档组织成一棵节点树,树是理解 DOM 全部操作的"地图"。
树的结构
<?xml version="1.0"?>
<company>
<employee id="101">
<name>张三</name>
<age>28</age>
</employee>
<employee id="102">
<name>李四</name>
<age>32</age>
</employee>
</company>
Document (文档根)
└── company (根元素)
├── employee (id="101")
│ ├── name → "张三"
│ └── age → "28"
└── employee (id="102")
├── name → "李四"
└── age → "32"
树的基本术语
- 根节点: 树的最顶层(文档节点)
- 父/子: 直接上下级关系
- 兄弟: 同一父节点的子节点
- 叶节点: 没有子节点的节点
- 深度: 从根到该节点的层数
JavaScript 访问节点树
// 通过 DOM 解析后:
var doc = parser.parseFromString(xmlText, "text/xml");
var root = doc.documentElement; // company
var employees = root.childNodes; // 子节点集合
// 遍历
for (var i = 0; i < root.childNodes.length; i++) {
var node = root.childNodes[i];
if (node.nodeType === 1) { // 元素
console.log(node.nodeName + " " +
node.getAttribute("id"));
}
}
// 输出: employee 101 / employee 102
为什么用树模型
树模型让文档的访问"结构化":层次关系天然清晰,遍历、搜索、修改都有统一规则。这也是 DOM 成为标准的原因。
DOM 解析
DOM 解析
解析(Parsing)是把 XML 文本转换为 DOM 节点树的过程。解析成功后才能用 DOM API 操作文档。
解析方式
# 1. 从字符串解析(JavaScript 浏览器)
var parser = new DOMParser();
var doc = parser.parseFromString(xmlText, "text/xml");
# 2. 从文件加载(通过 XMLHttpRequest)
var xhr = new XMLHttpRequest();
xhr.open("GET", "data.xml", false); // 同步
xhr.send();
var doc = xhr.responseXML; // 已解析的 DOM
# 3. Python 解析(示例)
from xml.dom import minidom
doc = minidom.parse("data.xml") # 文件
doc = minidom.parseString(xml_text) # 字符串
解析错误处理
// JavaScript:检查解析是否成功
var doc = parser.parseFromString(badXml, "text/xml");
var error = doc.getElementsByTagName("parsererror");
if (error.length > 0) {
console.log("XML 解析失败: " + error[0].textContent);
} else {
console.log("解析成功");
}
XML 语法要求
解析器严格要求 XML 语法,常见错误:
- 标签未闭合: <book>...</book> 必须配对
- 大小写敏感: <Book> 与 <book> 不同
- 属性必须加引号: id="1" 不能写成 id=1
- 正确嵌套: 不能交错 <a><b></a></b>
- 特殊字符转义: < 用 < , & 用 &
验证解析结果
// 解析后检查根元素
console.log(doc.documentElement.nodeName);
// 应输出 XML 文档的根元素名
// 遍历验证
var titles = doc.getElementsByTagName("title");
console.log("title 数量: " + titles.length);
解析是 DOM 操作的第一步:字符串/文件 → 节点树。解析成功与否,决定了后续所有操作能否进行。
DOM 加载函数
DOM 加载函数
把 XML 文档加载进 DOM 是解析的关键环节。本节介绍常用语言中的 XML 加载函数。
JavaScript(浏览器)
// 方式1: DOMParser 解析字符串
var parser = new DOMParser();
var doc = parser.parseFromString(xmlString, "text/xml");
// 方式2: XMLHttpRequest 加载文件
function loadXMLDoc(filename) {
var xhr = new XMLHttpRequest();
xhr.open("GET", filename, false);
xhr.send();
return xhr.responseXML;
}
var doc = loadXMLDoc("books.xml");
XMLHttpRequest 细节
var xhr = new XMLHttpRequest();
xhr.open("GET", "books.xml", false); // false=同步
// 同步模式下,send 后直接可用
xhr.send();
var doc = xhr.responseXML;
// 异步模式(推荐,不阻塞)
xhr.open("GET", "books.xml", true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
var doc = xhr.responseXML;
// 处理文档
}
};
xhr.send();
Python 加载
from xml.dom import minidom
# 从文件
doc = minidom.parse("books.xml")
# 从字符串
doc = minidom.parseString(
"<book><title>Ruby</title></book>"
)
Node.js 加载
// 使用 xmldom 库
const { DOMParser } = require("xmldom");
const fs = require("fs");
const xml = fs.readFileSync("books.xml", "utf-8");
const doc = new DOMParser().parseFromString(xml, "text/xml");
加载后的操作入口
// 加载完成后,统一从 documentElement 开始
var root = doc.documentElement;
// 常用入口方法
doc.getElementsByTagName("book") // 按标签名取集合
doc.getElementsByTagNameNS(ns, "book") // 按命名空间
doc.getElementById(id) // 按 ID(XML 不常用)
加载函数是把外部 XML 变成 DOM 的"入口"。浏览器用 XHR/DOMParser,后端用各自的 XML 库,概念相同。
DOM 方法
DOM 方法
DOM 提供一套标准方法用于操作节点与文档。本节汇总最常用的 DOM 方法。
节点操作方法
// 获取节点
getElementsByTagName("title") // 按标签名返回集合
getElementsByTagNameNS(ns, "t") // 按命名空间
getAttribute("id") // 获取属性值
getAttributeNode("id") // 获取属性节点
// 修改节点
setAttribute("id", "100") // 设置属性
removeAttribute("id") // 删除属性
nodeValue / textContent // 修改文本
// 创建节点
createElement("book") // 创建元素
createTextNode("内容") // 创建文本
createAttribute("id") // 创建属性
createComment("注释") // 创建注释
createCDATASection("...") // 创建 CDATA
// 增删改节点
appendChild(newNode) // 添加子节点(末尾)
insertBefore(newNode, refNode) // 在指定节点前插入
removeChild(oldNode) // 删除子节点
replaceChild(newNode, oldNode) // 替换子节点
cloneNode(deep) // 克隆(deep=true 深克隆)
节点关系方法
// 判断与查找
hasChildNodes() // 是否有子节点
hasAttribute("id") // 是否有某属性
contains(node) // 是否包含某节点(HTML5)
// 遍历
node.childNodes // 子节点集合
node.parentNode // 父节点
node.firstChild // 第一个子节点
node.lastChild // 最后一个子节点
node.nextSibling // 下一个兄弟
node.previousSibling // 上一个兄弟
文档级方法
// Document 对象的方法
doc.documentElement // 根元素
doc.createElement(...) // 创建元素
doc.importNode(node, true) // 从其他文档导入节点
doc.getElementsByTagName(...)
doc.getElementById(...)
完整示例
// 创建一个新 book 节点并添加到文档
var doc = loadXMLDoc("books.xml");
var root = doc.documentElement;
var newBook = doc.createElement("book");
newBook.setAttribute("id", "3");
var title = doc.createElement("title");
title.appendChild(doc.createTextNode("Ruby 进阶"));
newBook.appendChild(title);
root.appendChild(newBook);
DOM 方法覆盖"查、改、建、删"四大类操作。把常用方法记熟,后续章节的节点操作就是组合运用。
DOM 访问
DOM 访问
访问(DOM Access)是读取文档内容的操作:查找元素、读取属性与文本。本节介绍常用的访问方式。
按标签名访问
var doc = loadXMLDoc("books.xml");
// 获取所有 title 元素
var titles = doc.getElementsByTagName("title");
// 遍历读取文本
for (var i = 0; i < titles.length; i++) {
console.log(titles[i].childNodes[0].nodeValue);
}
// 或简洁写法
console.log(titles[i].textContent);
读取属性
<book id="1" category="web">...</book>
var book = doc.getElementsByTagName("book")[0];
// 读取属性值
var id = book.getAttribute("id"); // "1"
var cat = book.getAttribute("category"); // "web"
// 判断是否有属性
if (book.hasAttribute("category")) { ... }
// 遍历所有属性
var attrs = book.attributes; // NamedNodeMap
for (var i = 0; i < attrs.length; i++) {
console.log(attrs[i].nodeName + "=" +
attrs[i].nodeValue);
}
读取文本内容
<title>Ruby 入门</title>
// 方法1: 通过子文本节点
var title = doc.getElementsByTagName("title")[0];
var text = title.childNodes[0].nodeValue; // Ruby 入门
// 方法2: textContent(简洁,推荐)
var text = title.textContent;
// 注意: childNodes[0] 假设第一个子节点是文本
// 若元素内嵌套子元素,用 textContent 更安全
访问节点信息
var node = doc.getElementsByTagName("book")[0];
node.nodeName // "book"(元素名)
node.nodeType // 1(元素)
node.nodeValue // null(元素节点的值为 null)
node.tagName // "book"(元素专用,等同 nodeName)
// 文本节点
var textNode = node.childNodes[0];
textNode.nodeName // "#text"
textNode.nodeValue // 文本内容
遍历访问
// 递归遍历所有元素
function walk(node) {
if (node.nodeType === 1) {
console.log(node.nodeName);
}
var kids = node.childNodes;
for (var i = 0; i < kids.length; i++) {
walk(kids[i]);
}
}
walk(doc.documentElement);
访问是 DOM 最频繁的操作。核心三问:找什么(标签/属性/文本)、怎么找(getElementsByTagName 等)、怎么读(nodeValue/textContent)。
DOM 节点信息
DOM 节点信息
每个 DOM 节点都携带描述自身的信息:名称、值、类型。本节详解节点的三大基础属性。
nodeName(节点名称)
节点类型 nodeName 取值
元素节点 标签名(如 "book")
属性节点 属性名(如 "id")
文本节点 "#text"
注释节点 "#comment"
CDATA 节点 "#cdata-section"
文档节点 "#document"
var node = doc.documentElement; // company 元素
node.nodeName; // "company"
nodeValue(节点值)
节点类型 nodeValue 取值
元素节点 null
属性节点 属性值(如 "101")
文本节点 文本内容
注释节点 注释内容
// 示例:读取文本
var title = doc.getElementsByTagName("title")[0];
title.childNodes[0].nodeValue; // "Ruby 入门"
// 示例:读取属性值
var book = doc.getElementsByTagName("book")[0];
book.getAttributeNode("id").nodeValue; // "1"
nodeType(节点类型)
常见 nodeType 常量:
1 元素节点
2 属性节点
3 文本节点
4 CDATA 节点
5 实体引用
6 实体
7 处理指令
8 注释节点
9 文档节点
10 文档类型
11 文档片段
12 记号
// 判断示例
if (node.nodeType === 1) {
// 元素节点处理
} else if (node.nodeType === 3) {
// 文本节点处理
}
综合示例
// 遍历并打印所有节点信息
function showNodeInfo(node) {
console.log("名称: " + node.nodeName);
console.log("类型: " + node.nodeType);
console.log("值: " + node.nodeValue);
console.log("---");
}
// 遍历文档
var nodes = doc.getElementsByTagName("*");
for (var i = 0; i < nodes.length; i++) {
showNodeInfo(nodes[i]);
}
其他常用信息
// 元素特有
element.tagName // 标签名(等同 nodeName)
element.attributes // 属性集合(NamedNodeMap)
element.childNodes // 子节点集合
// 文档
doc.documentElement // 根元素
doc.nodeName // "#document"
节点信息三件套(nodeName/nodeValue/nodeType)是识别与处理节点的钥匙。写遍历代码时,先判断类型再取数据,最稳妥。
DOM 节点列表
DOM 节点列表
NodeList 是节点集合对象,由 getElementsByTagName 等方法返回。理解 NodeList 的特性,才能正确遍历。
NodeList 是什么
var titles = doc.getElementsByTagName("title");
// titles 是一个 NodeList(节点列表)
// 类似数组,但不是真正的 Array
titles.length // 节点数量
titles[0] // 第一个节点(索引访问)
titles.item(0) // 等价写法
遍历 NodeList
// 方式1: for 循环(经典)
for (var i = 0; i < titles.length; i++) {
console.log(titles[i].textContent);
}
// 方式2: for...of(现代浏览器)
for (var t of titles) {
console.log(t.textContent);
}
// 方式3: 转为数组(可用数组方法)
var arr = Array.prototype.slice.call(titles);
arr.forEach(function(t) {
console.log(t.textContent);
});
NodeList 的特点
// 1. 可能是"活的"(live)
// getElementsByTagName 返回的是活集合:
// 文档变化时,集合自动更新
var books = doc.getElementsByTagName("book");
console.log(books.length); // 2
doc.documentElement.appendChild(newBook);
console.log(books.length); // 3 (自动更新)
// 2. 索引与长度
books[0] / books.item(0) // 第一个
books[books.length - 1] // 最后一个
过滤非元素节点
// getElementsByTagName 只返回元素节点
// 但 childNodes 会包含文本节点等
var children = element.childNodes;
for (var i = 0; i < children.length; i++) {
if (children[i].nodeType === 1) { // 只处理元素
console.log(children[i].nodeName);
}
}
// 更简便:children 属性(仅元素子节点)
var kids = element.children; // 只含元素
NodeList 与 NamedNodeMap 的区别
// NodeList: 节点集合(元素、文本等)
getElementsByTagName("title") → NodeList
// NamedNodeMap: 属性节点集合
element.attributes → NamedNodeMap
// 有 getNamedItem/getAttributeNode 等方法
NodeList 是 DOM 中最常见的集合类型。记住三点:索引访问、length、可能是活集合。
DOM 遍历
DOM 遍历
遍历(Traversal)是按特定顺序访问节点树的过程:从前到后、从上到下,访问每个需要处理的节点。
遍历方式
# 1. 递归遍历(深度优先)
function walkTree(node, depth) {
// 处理当前节点
var indent = " ".repeat(depth);
if (node.nodeType === 1) {
console.log(indent + node.nodeName);
}
// 递归子节点
var kids = node.childNodes;
for (var i = 0; i < kids.length; i++) {
walkTree(kids[i], depth + 1);
}
}
walkTree(doc.documentElement, 0);
只遍历元素节点
function walkElements(node, depth) {
if (node.nodeType === 1) {
console.log(" ".repeat(depth) + node.nodeName);
var kids = node.childNodes;
for (var i = 0; i < kids.length; i++) {
walkElements(kids[i], depth + 1);
}
}
}
// 或使用元素专属集合
function walkByChildren(node, depth) {
console.log(" ".repeat(depth) + node.nodeName);
var kids = node.children; // 仅元素
for (var i = 0; i < kids.length; i++) {
walkByChildren(kids[i], depth + 1);
}
}
按条件收集节点
// 收集所有含文本的元素
function collectTextElements(node, result) {
if (node.nodeType === 1 && node.childNodes.length === 1
&& node.firstChild.nodeType === 3) {
result.push(node);
}
for (var i = 0; i < node.childNodes.length; i++) {
collectTextElements(node.childNodes[i], result);
}
return result;
}
var textElems = collectTextElements(
doc.documentElement, []);
XPath 遍历(更强大)
// 部分实现支持 XPath 查询
// Python 示例
from xml.dom import minidom
from xml.etree import ElementTree
tree = ElementTree.parse("books.xml")
for book in tree.iter("book"): # 遍历所有 book
print(book.get("id"))
# XPath
for title in tree.findall(".//title"):
print(title.text)
遍历注意事项
# 1. 修改时遍历: 删除节点时反向遍历
# 从后往前删,避免索引错乱
for (var i = nodes.length - 1; i >= 0; i--) {
nodes[i].parentNode.removeChild(nodes[i]);
}
# 2. 活集合遍历: 注意 length 会变
# 先复制快照再遍历更安全
var snapshot = Array.prototype.slice.call(nodes);
遍历是 DOM 编程的基础功。递归 + 类型判断是最通用组合;需要复杂查询时,考虑 XPath 或 ElementTree 等高级接口。
DOM 导航
DOM 导航
导航(Navigation)是通过节点之间的关系(父、子、兄弟)在树中移动,而非按标签名搜索。掌握导航,可以精确定位相邻节点。
导航属性
// 父节点
node.parentNode // 父节点(null 表示是根)
// 子节点
node.childNodes // 所有子节点(NodeList)
node.firstChild // 第一个子节点
node.lastChild // 最后一个子节点
// 兄弟节点
node.nextSibling // 下一个兄弟
node.previousSibling // 上一个兄弟
// 文档根
doc.documentElement // 根元素
示例 XML
<bookstore>
<book id="1">
<title>Ruby 入门</title>
<price>59</price>
</book>
<book id="2">
<title>Python 基础</title>
<price>49</price>
</book>
</bookstore>
导航示例
var root = doc.documentElement; // bookstore
root.childNodes // 所有子节点(含空白文本)
// 找到第一个 book 元素
var books = root.getElementsByTagName("book");
var book1 = books[0];
book1.parentNode.nodeName // "bookstore"
book1.nextSibling // 可能是空白文本节点
book1.nextSibling.nodeName // "#text"(如果有空白)
// 直接取元素兄弟
book1.nextElementSibling // book2(仅元素)
book1.previousElementSibling // null
book1.children[0].nodeName // "title"
空白节点问题
// 格式化 XML 中的缩进会产生文本节点!
// 解决方案:
// 1. 使用 children / nextElementSibling(仅元素)
// 2. 遍历时过滤 nodeType === 1
// 3. 压缩 XML(无空白)
function firstElementChild(node) {
var kids = node.childNodes;
for (var i = 0; i < kids.length; i++) {
if (kids[i].nodeType === 1) return kids[i];
}
return null;
}
导航到指定位置
// 找到 title 后导航到兄弟 price
var title = doc.getElementsByTagName("title")[0];
var parent = title.parentNode; // book
var price = parent.children[1]; // price 元素
console.log(price.textContent); // "59"
// 或通过 nextElementSibling
var price2 = title.nextElementSibling; // price
注意事项
// childNodes 包含文本、注释等所有节点
// children 只含元素子节点
// 导航时优先用元素相关的属性,减少类型判断
// parentNode 永远存在(除 document)
// 移动节点: 用 appendChild 到新父节点即可
// (节点会自动从原位置移除)
导航与"按标签搜索"互补:搜索适合"按名字找",导航适合"沿关系走"。实际开发常两者混用。
DOM 获取节点
DOM 获取节点
获取节点是 DOM 操作的基础:按标签、按属性、按关系找到目标节点。本节系统介绍获取节点的方法。
按标签名获取
var doc = loadXMLDoc("books.xml");
// 获取所有指定标签元素
var books = doc.getElementsByTagName("book");
// 从指定元素下获取(局部搜索)
var book1 = books[0];
var titles = book1.getElementsByTagName("title");
// 通配符获取所有元素
var all = doc.getElementsByTagName("*");
按属性获取
// 注意: getElementById 对 XML 不保证可用
// (XML 没有 ID 类型属性概念)
// 通常需要自己遍历查找属性
function getByAttr(root, tag, attrName, attrValue) {
var nodes = root.getElementsByTagName(tag);
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].getAttribute(attrName) === attrValue) {
return nodes[i];
}
}
return null;
}
var book = getByAttr(doc.documentElement,
"book", "id", "2");
通过导航获取
var root = doc.documentElement;
root.firstElementChild // 第一个子元素
root.lastElementChild // 最后一个子元素
root.children[1] // 第二个子元素
// 兄弟导航
var node = someNode;
node.nextElementSibling
node.previousElementSibling
获取文本与属性
// 文本
var title = doc.getElementsByTagName("title")[0];
title.textContent // 文本(推荐)
title.childNodes[0].nodeValue // 第一个文本子节点
// 属性
var book = doc.getElementsByTagName("book")[0];
book.getAttribute("id") // "1"
book.getAttributeNode("id") // 属性节点对象
book.hasAttribute("id") // true
高级: XPath(部分环境)
// 浏览器可用 evaluate 做 XPath 查询
var xpathResult = doc.evaluate(
"//book[@id='2']/title",
doc, null,
XPathResult.FIRST_ORDERED_NODE_TYPE, null);
var title = xpathResult.singleNodeValue;
console.log(title.textContent); // Python 基础
选择方法的取舍
| 需求 | 推荐方法 |
|--------------------|-----------------------------|
| 按标签取集合 | getElementsByTagName |
| 精确到单个节点 | 遍历 + 属性判断 |
| 相邻节点 | next/previousElementSibling |
| 复杂条件查询 | XPath(如支持) |
| 只取元素 | children |
获取节点的方法组合:标签搜索 + 属性判断 + 关系导航。按场景选最直接的方式,代码更清晰。
DOM 改变节点
DOM 改变节点
改变节点包括:修改文本内容、修改属性、修改元素名。本节介绍常见的节点修改操作。
改变文本
var title = doc.getElementsByTagName("title")[0];
// 方式1: 修改文本节点的 nodeValue
title.firstChild.nodeValue = "Ruby 高级教程";
// 方式2: textContent(简洁,推荐)
title.textContent = "Ruby 高级教程";
// 方式3: 删除旧节点,插入新节点
title.removeChild(title.firstChild);
title.appendChild(doc.createTextNode("Ruby 高级教程"));
改变属性
var book = doc.getElementsByTagName("book")[0];
// 新增/修改属性
book.setAttribute("category", "web");
book.setAttribute("id", "100");
// 修改属性节点
var attrNode = book.getAttributeNode("id");
attrNode.nodeValue = "200";
// 删除属性
book.removeAttribute("category");
改变元素名
// DOM 没有直接的"改元素名"方法
// 常用做法: 创建新元素,复制内容,替换旧元素
function renameElement(oldElem, newName) {
var doc = oldElem.ownerDocument;
var newElem = doc.createElement(newName);
// 复制属性
for (var i = 0; i < oldElem.attributes.length; i++) {
var attr = oldElem.attributes[i];
newElem.setAttribute(attr.nodeName, attr.nodeValue);
}
// 复制子节点
while (oldElem.firstChild) {
newElem.appendChild(oldElem.firstChild);
}
// 替换
oldElem.parentNode.replaceChild(newElem, oldElem);
return newElem;
}
改变节点值(文本节点)
// 直接操作文本节点
var textNode = title.firstChild;
if (textNode.nodeType === 3) {
textNode.nodeValue = "新内容";
}
// 元素节点的 nodeValue 是 null,不能直接改
// 元素文本必须通过子文本节点或 textContent 修改
修改验证
// 修改后验证
console.log(title.textContent); // 新内容
console.log(book.getAttribute("id")); // 新值
// 序列化查看(部分环境)
var xmlString = new XMLSerializer()
.serializeToString(doc);
console.log(xmlString);
改变节点的核心:文本用 textContent/nodeValue,属性用 setAttribute/removeAttribute,元素名用"创建新元素替换"。
DOM 删除节点
DOM 删除节点
删除节点是从文档树中移除节点的操作。删除前务必先找到父节点,因为 DOM 要求通过父节点删除子节点。
删除元素节点
// 基本流程: 父节点.removeChild(子节点)
var book = doc.getElementsByTagName("book")[0];
var parent = book.parentNode; // bookstore
parent.removeChild(book);
// 更简洁: 直接通过 parentNode
book.parentNode.removeChild(book);
删除文本节点
var title = doc.getElementsByTagName("title")[0];
var textNode = title.firstChild;
if (textNode && textNode.nodeType === 3) {
title.removeChild(textNode); // 删除文本
}
// 此时 title 元素仍在,但内容为空
删除属性
var book = doc.getElementsByTagName("book")[0];
// 方式1: removeAttribute
book.removeAttribute("category");
// 方式2: 通过属性节点
var attrNode = book.getAttributeNode("category");
if (attrNode) {
book.removeAttributeNode(attrNode);
}
// 删除所有属性
while (book.attributes.length > 0) {
book.removeAttributeNode(book.attributes[0]);
}
清空元素内容
// 删除所有子节点
function removeAllChildren(elem) {
while (elem.firstChild) {
elem.removeChild(elem.firstChild);
}
}
removeAllChildren(title);
// 或一次赋值
title.textContent = ""; // 清空文本(保留元素)
批量删除与活集合
// 删除所有 book(注意活集合陷阱)
// ❌ 错误: length 会动态变化
for (var i = 0; i < books.length; i++) {
books[i].parentNode.removeChild(books[i]);
}
// ✅ 正确: 从后往前删,或循环判断
while (books.length > 0) {
books[0].parentNode.removeChild(books[0]);
}
// 或先复制快照
var snapshot = Array.prototype.slice.call(books);
snapshot.forEach(function(b) {
b.parentNode.removeChild(b);
});
删除后的状态
// 被删除的节点仍在内存中(可再插入)
var removed = book.parentNode.removeChild(book);
// 重新插入到其他位置
doc.documentElement.appendChild(removed);
删除节点的铁律:通过父节点删、注意活集合、删后节点对象仍可用。掌握这三条,删除操作不会出错。
DOM 替换节点
DOM 替换节点
替换节点是用新节点替代旧节点的操作。DOM 提供 replaceChild 方法完成这一任务。
replaceChild 基本用法
// 语法: parent.replaceChild(新节点, 旧节点)
var oldBook = doc.getElementsByTagName("book")[0];
// 创建新节点
var newBook = doc.createElement("book");
newBook.setAttribute("id", "99");
newBook.textContent = "新书";
// 替换
oldBook.parentNode.replaceChild(newBook, oldBook);
替换文本内容
// 场景: 替换元素内的文本
function replaceText(elem, newText) {
// 清空
while (elem.firstChild) {
elem.removeChild(elem.firstChild);
}
// 添加新文本
elem.appendChild(
elem.ownerDocument.createTextNode(newText));
}
replaceText(title, "全新标题");
替换属性值
// setAttribute 本身就是"替换或新增"
book.setAttribute("id", "777"); // 旧值被替换
// 通过属性节点替换
var oldAttr = book.getAttributeNode("id");
var newAttr = doc.createAttribute("id");
newAttr.nodeValue = "888";
book.replaceChild(newAttr, oldAttr);
// 或: book.setAttributeNode(newAttr);
复制并替换(内容克隆)
// 用已有节点的副本替换
var source = doc.getElementsByTagName("book")[1];
var clone = source.cloneNode(true); // 深克隆
var target = doc.getElementsByTagName("book")[0];
target.parentNode.replaceChild(clone, target);
跨文档替换
// 从另一个文档导入节点再替换
var otherDoc = loadXMLDoc("other.xml");
var imported = doc.importNode(
otherDoc.getElementsByTagName("book")[0], true);
var target = doc.getElementsByTagName("book")[0];
target.parentNode.replaceChild(imported, target);
替换验证
// 验证替换结果
var books = doc.getElementsByTagName("book");
console.log(books.length); // 数量不变
console.log(books[0].getAttribute("id")); // 新值
// 旧节点已被移除,但对象仍可访问
console.log(oldBook.nodeName); // 仍可用
替换的核心:replaceChild(新, 旧)。新节点可以现建、克隆或跨文档导入,替换后旧节点对象仍留在内存。
DOM 创建节点
DOM 创建节点
创建节点是向文档添加内容的前提:创建元素、文本、属性、注释等各类节点。
创建各类节点
var doc = loadXMLDoc("books.xml");
// 创建元素节点
var newBook = doc.createElement("book");
// 创建文本节点
var text = doc.createTextNode("Ruby 高级");
// 创建属性节点
var attr = doc.createAttribute("category");
attr.nodeValue = "programming";
// 创建注释节点
var comment = doc.createComment("这是一本新书");
// 创建 CDATA 节点
var cdata = doc.createCDATASection(
"含 <特殊> 字符的内容");
// 创建文档片段(批量添加优化)
var frag = doc.createDocumentFragment();
组装节点
// 完整创建一本书
var doc = loadXMLDoc("books.xml");
var book = doc.createElement("book");
book.setAttribute("id", "5");
book.setAttribute("category", "web");
var title = doc.createElement("title");
title.appendChild(
doc.createTextNode("JavaScript 高级"));
book.appendChild(title);
var price = doc.createElement("price");
price.appendChild(doc.createTextNode("79"));
book.appendChild(price);
// 添加到文档
doc.documentElement.appendChild(book);
使用文档片段批量添加
// 大量添加时用 DocumentFragment 提升性能
var frag = doc.createDocumentFragment();
for (var i = 0; i < 100; i++) {
var b = doc.createElement("book");
b.setAttribute("id", i);
b.textContent = "Book " + i;
frag.appendChild(b);
}
// 一次添加到文档(只触发一次更新)
doc.documentElement.appendChild(frag);
创建属性并设置
// 方式1: createAttribute + nodeValue
var attr = doc.createAttribute("lang");
attr.nodeValue = "zh";
book.setAttributeNode(attr);
// 方式2: setAttribute 一步到位(推荐)
book.setAttribute("lang", "zh");
创建后的操作
// 新创建的节点默认是"游离"的(不在文档中)
// 必须通过插入操作挂到树上:
// - appendChild: 添加到末尾
// - insertBefore: 插入到指定位置
// - replaceChild: 替换已有节点
// 判断是否在文档中
book.isConnected // true=已在文档中(HTML5)
创建节点 = 造零件,插入操作 = 组装。先 create 再 append/insert,是 DOM 添加内容的标准流程。
DOM 添加节点
DOM 添加节点
添加节点是把新节点插入文档树的操作。核心方法:appendChild 与 insertBefore。
appendChild(末尾添加)
var root = doc.documentElement;
var newBook = doc.createElement("book");
newBook.textContent = "新书";
// 添加到末尾
root.appendChild(newBook);
// 返回被添加的节点
var added = root.appendChild(newBook);
insertBefore(指定位置插入)
// 语法: parent.insertBefore(新节点, 参考节点)
// 新节点插到参考节点之前
var root = doc.documentElement;
var firstBook = root.getElementsByTagName("book")[0];
var newBook = doc.createElement("book");
newBook.textContent = "插到最前";
root.insertBefore(newBook, firstBook);
插入到末尾/开头工具
// 末尾: appendChild
// 开头: insertBefore(新, firstChild)
function prepend(parent, newNode) {
parent.insertBefore(newNode, parent.firstChild);
}
function insertAfter(parent, newNode, refNode) {
// 插到 refNode 之后
parent.insertBefore(newNode,
refNode.nextSibling);
}
移动已有节点
// appendChild/insertBefore 会自动"移动"节点
// 原位置自动移除,插入到新位置
var book1 = doc.getElementsByTagName("book")[0];
// 把 book1 移到末尾
doc.documentElement.appendChild(book1);
// book1 自动从原位置移除,添加到末尾
添加文本与属性
// 给元素添加文本
var title = doc.createElement("title");
title.appendChild(doc.createTextNode("内容"));
// 或 textContent(更简洁)
title.textContent = "内容";
// 添加属性
book.setAttribute("id", "101");
// 属性用 setAttribute,不走 appendChild
添加后验证
// 验证位置与数量
var books = doc.getElementsByTagName("book");
console.log(books.length);
console.log(books[0].textContent); // 检查顺序
// 父节点验证
newBook.parentNode.nodeName; // "bookstore"
添加节点的要点:appendChild 加末尾、insertBefore 插指定位置、已有节点会被自动移动。属性用 setAttribute 单独设置。
DOM 克隆节点
DOM 克隆节点
克隆节点是复制已有节点创建副本的操作,用于"复制模板"式地快速生成结构相似的节点。
cloneNode 基本用法
// 语法: node.cloneNode(deep)
// deep=true: 深克隆(含子节点)
// deep=false: 浅克隆(仅自身)
var book = doc.getElementsByTagName("book")[0];
// 深克隆(复制 book 及其所有子节点)
var clone = book.cloneNode(true);
// 浅克隆(只有 book 元素本身,无子节点)
var shallow = book.cloneNode(false);
// 克隆节点是"游离"的,需插入
doc.documentElement.appendChild(clone);
深克隆 vs 浅克隆
<book id="1">
<title>Ruby</title>
</book>
// 浅克隆结果
<book id="1"></book> // 属性复制,子节点不复制
// 深克隆结果
<book id="1">
<title>Ruby</title>
</book> // 全部复制
克隆后修改
// 模板式生成:克隆后改内容
var template = doc.getElementsByTagName("book")[0];
function cloneBook(id, titleText) {
var clone = template.cloneNode(true);
clone.setAttribute("id", id);
clone.getElementsByTagName("title")[0]
.textContent = titleText;
return clone;
}
doc.documentElement.appendChild(cloneBook("7", "新书"));
克隆注意事项
// 1. 事件监听不会被克隆(属性 onclick 会)
// 2. 克隆节点的 ownerDocument 保持不变
// 3. 跨文档克隆用 importNode 更规范
// 跨文档克隆
var otherDoc = loadXMLDoc("template.xml");
var tpl = otherDoc.getElementsByTagName("book")[0];
var imported = doc.importNode(tpl, true);
doc.documentElement.appendChild(imported);
克隆节点实例
// 复制整个 book 集合并做差异处理
var books = doc.getElementsByTagName("book");
var frag = doc.createDocumentFragment();
for (var i = 0; i < books.length; i++) {
var c = books[i].cloneNode(true);
c.setAttribute("copy", "true"); // 标记副本
frag.appendChild(c);
}
doc.documentElement.appendChild(frag);
克隆节点的价值:模板化生成。"克隆 + 修改 + 插入"三步组合,是批量创建结构相似节点的标准姿势。
DOM XMLHttpRequest
DOM XMLHttpRequest
XMLHttpRequest(XHR)是浏览器中发起 HTTP 请求、加载 XML/JSON 数据的核心对象,也是 AJAX 技术的基石。
基本用法
var xhr = new XMLHttpRequest();
xhr.open("GET", "books.xml", true); // 异步
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) { // 请求完成
if (xhr.status === 200) { // 成功
var doc = xhr.responseXML; // XML DOM
console.log(
doc.documentElement.nodeName);
} else {
console.log("错误: " + xhr.status);
}
}
};
xhr.send();
readyState 状态
0 UNSENT 未初始化(open 未调用)
1 OPENED 已调用 open
2 HEADERS_RECEIVED 已收到响应头
3 LOADING 正在接收响应体
4 DONE 请求完成
加载 XML 的两种方式
// 方式1: 同步加载(简单但阻塞)
xhr.open("GET", "books.xml", false);
xhr.send();
var doc = xhr.responseXML;
// 方式2: 异步加载(推荐)
xhr.open("GET", "books.xml", true);
xhr.onload = function() {
if (xhr.status === 200) {
var doc = xhr.responseXML;
// 处理文档
}
};
xhr.send();
加载 JSON 与其他数据
// JSON
xhr.open("GET", "data.json", true);
xhr.responseType = "json";
xhr.onload = function() {
console.log(xhr.response); // 已解析的对象
};
xhr.send();
// 文本
xhr.open("GET", "readme.txt", true);
xhr.onload = function() {
console.log(xhr.responseText);
};
xhr.send();
发送数据(POST)
var xhr = new XMLHttpRequest();
xhr.open("POST", "/api/books", true);
xhr.setRequestHeader("Content-Type",
"application/x-www-form-urlencoded");
xhr.onload = function() {
console.log(xhr.responseText);
};
xhr.send("id=10&title=新书");
常见问题
// 1. 跨域: 同源策略限制
// 解决: 服务端 CORS 或代理
// 2. 本地文件: file:// 协议下 XHR 受限
// 解决: 本地起服务器(如 python -m http.server)
// 3. 响应不是 XML: 检查 Content-Type
// 解决: xhr.overrideMimeType("text/xml")
现代替代 fetch
// fetch API 更现代简洁
fetch("books.xml")
.then(r => r.text())
.then(text => {
var doc = new DOMParser()
.parseFromString(text, "text/xml");
// 处理
});
XHR 是把远程 XML 加载进 DOM 的标准途径。掌握 readyState 判断与异步模式,配合 responseXML 就能实现"远程数据 → DOM 操作"的完整链路。
DOM 节点类型
DOM 节点类型
DOM 标准定义了 12 种节点类型,每种对应一个常量值。本节完整介绍各类型及其用途。
节点类型常量表
常量名 值 说明
ELEMENT_NODE 1 元素节点
ATTRIBUTE_NODE 2 属性节点
TEXT_NODE 3 文本节点
CDATA_SECTION_NODE 4 CDATA 段
ENTITY_REFERENCE_NODE 5 实体引用
ENTITY_NODE 6 实体
PROCESSING_INSTRUCTION_NODE 7 处理指令
COMMENT_NODE 8 注释节点
DOCUMENT_NODE 9 文档节点
DOCUMENT_TYPE_NODE 10 文档类型(DOCTYPE)
DOCUMENT_FRAGMENT_NODE 11 文档片段
NOTATION_NODE 12 记号
// 访问方式
Node.ELEMENT_NODE // 1
node.nodeType === Node.TEXT_NODE // 3
各类型说明
// 元素节点(1): 最常见的节点
<book></book>
nodeName="book" nodeValue=null
// 属性节点(2): 元素上的属性
id="1"
nodeName="id" nodeValue="1"
// 文本节点(3): 元素内文本
"Ruby"
nodeName="#text" nodeValue="Ruby"
// CDATA(4): 含特殊字符的文本
<![CDATA[<b>粗体</b>]]>
// 注释(8)
<!-- 说明 -->
// 文档(9): 整棵树
nodeName="#document"
// 文档类型(10)
<!DOCTYPE books>
属性节点的特殊之处
// 属性节点不属于元素的 childNodes!
// 属性通过 attributes(NamedNodeMap)访问
var book = doc.getElementsByTagName("book")[0];
book.attributes // NamedNodeMap
book.attributes.length // 属性数量
book.attributes.getNamedItem("id")
// 属性节点有 parentNode 返回 null(部分实现)
判断节点类型的模式
// 常用判断模式
function isElement(node) {
return node.nodeType === 1;
}
function isText(node) {
return node.nodeType === 3;
}
// 遍历时过滤
for (var i = 0; i < node.childNodes.length; i++) {
var child = node.childNodes[i];
if (child.nodeType === 1) {
// 只处理元素
}
}
// 现代浏览器可用
node.nodeType === Node.ELEMENT_NODE
document 与 documentType
doc.nodeType // 9 (DOCUMENT_NODE)
doc.nodeName // "#document"
doc.documentElement // 根元素(1)
// 文档类型声明(如果存在)
var doctype = doc.doctype; // DOCUMENT_TYPE_NODE
if (doctype) {
console.log(doctype.name); // DOCTYPE 名称
}
节点类型是 DOM 世界的地基:所有遍历、过滤、判断都建立在 nodeType 之上。记住最常见的 1/2/3/8/9 五种即可应对 95% 场景。
DOM Node
DOM Node
Node 是所有 DOM 节点类型的基接口,定义了所有节点共有的属性与方法。本节系统介绍 Node 对象。
Node 概览
所有节点类型(元素、文本、属性、注释...)都继承自 Node。
Node 提供:
- 通用属性: nodeName/nodeValue/nodeType
- 关系属性: parentNode/childNodes/nextSibling
- 操作方法: appendChild/removeChild/cloneNode
Node 属性
// 基本信息
node.nodeName // 名称(标签名/#text 等)
node.nodeValue // 值(文本内容/属性值/null)
node.nodeType // 类型常量(1/2/3...)
node.localName // 本地名(去命名空间前缀)
node.prefix // 命名空间前缀
node.namespaceURI // 命名空间 URI
// 关系
node.parentNode // 父节点
node.childNodes // 子节点集合
node.firstChild // 第一个子节点
node.lastChild // 最后一个子节点
node.nextSibling // 下一个兄弟
node.previousSibling // 上一个兄弟
node.ownerDocument // 所属文档
// 状态
node.hasChildNodes() // 是否有子节点
node.isConnected // 是否在文档中
Node 方法
// 插入与删除
parent.appendChild(newChild) // 末尾添加
parent.insertBefore(new, ref) // 指定位置前插入
parent.removeChild(child) // 删除子节点
parent.replaceChild(new, old) // 替换子节点
// 复制
node.cloneNode(deep) // 克隆节点
// 比较与规范化
node.isEqualNode(other) // 是否相等
node.isSameNode(other) // 是否同一节点
node.normalize() // 合并相邻文本节点
// 判断
node.hasChildNodes()
node.contains(otherNode)
文本规范化 normalize
// 多次添加文本会形成多个文本节点
var p = doc.createElement("p");
p.appendChild(doc.createTextNode("Hello"));
p.appendChild(doc.createTextNode(" World"));
p.childNodes.length // 2
p.normalize(); // 合并相邻文本
p.childNodes.length // 1
p.textContent // "Hello World"
isEqualNode 示例
var a = doc.getElementsByTagName("book")[0];
var b = doc.getElementsByTagName("book")[1];
a.isEqualNode(b) // 内容相同返回 true
a.isSameNode(b) // false(不同对象)
a.isSameNode(a) // true(同一对象)
完整遍历示例(基于 Node)
// 用 Node 通用接口遍历整棵树
function walkNode(node, depth) {
var indent = " ".repeat(depth);
console.log(indent + node.nodeName +
" (" + node.nodeType + ")");
for (var i = 0; i < node.childNodes.length; i++) {
walkNode(node.childNodes[i], depth + 1);
}
}
walkNode(doc.documentElement, 0);
Node 是所有节点的"公共基类"。先把 Node 的通用接口掌握,再学 Element/Text 等子接口,就会非常轻松。
DOM NodeList
DOM NodeList
NodeList 是节点的有序集合,由 childNodes 与 getElementsByTagName 等返回。本节详解 NodeList 的接口与使用。
NodeList 基本接口
// 获取 NodeList
var list = element.childNodes;
var books = doc.getElementsByTagName("book");
// 核心接口
list.length // 节点数量
list.item(0) // 第 0 个节点
list[0] // 等价写法(索引访问)
list.item(list.length - 1) // 最后一个
获取 NodeList 的常见来源
element.childNodes // 所有子节点(含文本)
element.getElementsByTagName("x") // 后代元素集合
element.children // 仅元素子节点(HTML5)
document.querySelectorAll("x") // CSS 选择器(HTML DOM)
// 注意: childNodes 含所有类型节点
// getElementsByTagName 只含元素
活集合(Live)与静态集合
// 活集合: 文档变化时自动更新
var books = doc.getElementsByTagName("book");
books.length; // 2
doc.documentElement.appendChild(doc.createElement("book"));
books.length; // 3 (自动更新)
// childNodes 也是活集合
// querySelectorAll 是静态集合(不自动更新)
// 遍历活集合时注意 length 变化:
// 删除元素时从后往前遍历
遍历 NodeList
// 方式1: for 循环
for (var i = 0; i < list.length; i++) {
console.log(list[i].nodeName);
}
// 方式2: 转数组后使用数组方法
var arr = Array.prototype.slice.call(list);
arr.forEach(function(n) {
console.log(n.nodeName);
});
// 方式3: for...of(现代浏览器)
for (var n of list) { ... }
NodeList 与 Array 的区别
// NodeList 不是 Array:
// - 没有 push/pop/map/filter 等方法
// - 有 length 和索引访问
// 需要数组方法时先转换:
var arr = Array.from(list); // 现代
var arr = [].slice.call(list); // 传统
实践: 过滤元素并操作
// 获取某元素下所有 book,逐个修改
var root = doc.documentElement;
var books = root.getElementsByTagName("book");
// 转数组便于安全操作
Array.from(books).forEach(function(b, idx) {
b.setAttribute("index", idx);
});
NodeList 使用要点:length + 索引访问、可能是活集合、转数组后才能用数组方法。它是最常用的集合类型。
DOM NamedNodeMap
DOM NamedNodeMap
NamedNodeMap 是属性节点的集合,可通过名称访问。元素的 attributes 属性返回的就是 NamedNodeMap。
NamedNodeMap 概览
<book id="1" category="web" lang="zh">
var attrs = book.attributes;
// attrs 是 NamedNodeMap
attrs.length // 3
attrs[0] // 第一个属性节点
attrs.item(0) // 等价
attrs.getNamedItem("id") // 按名称获取属性节点
attrs.getNamedItem("id").nodeValue // "1"
常用方法
// 获取
attrs.getNamedItem("category") // 属性节点
attrs.item(1) // 按索引
attrs[1] // 等价
// 新增/替换
var newAttr = doc.createAttribute("rating");
newAttr.nodeValue = "5";
attrs.setNamedItem(newAttr); // 添加或替换同名
// 删除
attrs.removeNamedItem("category");
// 遍历
for (var i = 0; i < attrs.length; i++) {
var attr = attrs[i];
console.log(attr.nodeName + "=" + attr.nodeValue);
}
NamedNodeMap 与 NodeList 的区别
NamedNodeMap(属性集合) NodeList(节点集合)
- 可通过名称访问 getNamedItem - 只能按索引
- 成员通常是属性节点(2) - 成员是任意节点
- setNamedItem 可增改 - 无对应方法
- 顺序不保证 - 文档顺序
共同点:
- 都是活集合
- 都有 length/item/索引访问
常用属性操作模式
// 复制属性到新元素
function copyAttributes(from, to) {
var attrs = from.attributes;
for (var i = 0; i < attrs.length; i++) {
var a = attrs[i];
to.setAttribute(a.nodeName, a.nodeValue);
}
}
// 清空所有属性
function removeAllAttrs(elem) {
while (elem.attributes.length > 0) {
elem.removeAttribute(
elem.attributes[0].nodeName);
}
}
// 判断是否有属性
if (elem.hasAttributes()) { ... }
getAttributeNode 关联
// getAttributeNode 返回单一属性节点
var attrNode = book.getAttributeNode("id");
attrNode.nodeValue = "999"; // 直接改值(同步到元素)
// 等价于
book.setAttribute("id", "999");
// 注意: 修改 attrNode.nodeValue 会更新元素属性
// 两者效果一致
NamedNodeMap 是理解"属性"的窗口。日常用 setAttribute/getAttribute 即可,涉及遍历属性或属性节点操作时才直接使用 NamedNodeMap。
DOM Document
DOM Document
Document 对象代表整个 XML 文档,是节点树的根,也是所有节点的"所有者"。本节介绍 Document 的常用接口。
Document 概览
// 获取 Document 对象
var doc = xhr.responseXML; // XHR 加载
var doc = parser.parseFromString(s, "text/xml");
// 节点身份
doc.nodeType // 9 (DOCUMENT_NODE)
doc.nodeName // "#document"
doc.documentElement // 根元素
Document 属性
doc.documentElement // 根元素节点
doc.childNodes // 子节点(通常只有根元素)
doc.firstChild // 可能包含 XML 声明/DOCTYPE
doc.doctype // DOCTYPE(如有)
doc.implementation // DOMImplementation 对象
doc.URL // 文档 URL(HTML)
doc.title // 标题(HTML)
Document 创建方法
// 创建各类节点(重要!)
doc.createElement("book") // 元素
doc.createTextNode("内容") // 文本
doc.createAttribute("id") // 属性
doc.createComment("注释") // 注释
doc.createCDATASection("...") // CDATA
doc.createDocumentFragment() // 文档片段
doc.createProcessingInstruction("xml-stylesheet", "...")
// 创建后需插入文档树才生效
Document 查询方法
doc.getElementsByTagName("book") // 按标签名
doc.getElementsByTagNameNS(ns, "b") // 按命名空间
doc.getElementById("x") // 按 ID(HTML 常用)
// 按命名空间示例
var books = doc.getElementsByTagNameNS(
"http://example.com/ns", "book");
Document 导入与创建文档
// 从其他文档导入节点
var otherDoc = loadXMLDoc("other.xml");
var node = doc.importNode(
otherDoc.documentElement, true);
// 创建空白 XML 文档(部分实现)
var newDoc = doc.implementation.createDocument(
null, "root", null);
newDoc.documentElement.appendChild(
newDoc.createElement("item"));
序列化 Document
// 把 DOM 转回 XML 字符串
var serializer = new XMLSerializer();
var xmlString = serializer.serializeToString(doc);
console.log(xmlString);
// Node.js / 其他环境也有类似 API
Document 使用模式
// 典型流程
// 1. 加载: XHR/DOMParser 得到 doc
// 2. 查询: documentElement / getElementsByTagName
// 3. 创建: doc.createElement / createTextNode
// 4. 修改: appendChild / setAttribute
// 5. 序列化: XMLSerializer 输出
// 所有创建方法必须通过 doc 调用!
// 用错文档创建节点会报错或产生孤儿节点
Document 是 DOM 世界的"总入口":承载根元素、提供创建与查询方法。牢记"所有节点都由所属文档创建"这一规则。
DOM DocumentImpl
DOM DocumentImpl
DocumentImpl 是 DOMImplementation 接口的实现类,提供文档类型检测与文档创建等底层能力。
DOMImplementation 是什么
// DOMImplementation 提供与文档实例无关的操作
// 通过 document.implementation 访问
var impl = doc.implementation;
// 能力检测
impl.hasFeature("XML", "1.0") // true/false
impl.hasFeature("Core", "2.0")
impl.hasFeature("Events", "2.0")
createDocument 创建文档
// 创建新的 XML 文档
var newDoc = doc.implementation.createDocument(
namespaceURI, // 命名空间(无则 null)
qualifiedName, // 根元素名(如 "root")
doctype // 文档类型(无则 null)
);
// 示例: 创建根元素为 root 的文档
var d = doc.implementation.createDocument(
null, "root", null);
d.documentElement.nodeName; // "root"
// 创建带命名空间的文档
var nsDoc = doc.implementation.createDocument(
"http://example.com/xml", "ns:root", null);
createDocumentType
// 创建 DOCTYPE
var doctype = doc.implementation.createDocumentType(
"bookstore", // 名称
"SYSTEM", // publicId
"bookstore.dtd" // systemId
);
// 与文档关联
var d = doc.implementation.createDocument(
null, "bookstore", doctype);
// 序列化时 DOCTYPE 会输出:
// <!DOCTYPE bookstore SYSTEM "bookstore.dtd">
hasFeature 实践
// 特性检测(不同环境支持不同)
var features = [
["XML", "1.0"],
["Core", "2.0"],
["HTML", "2.0"]
];
for (var i = 0; i < features.length; i++) {
console.log(features[i].join(" ") + ": " +
doc.implementation.hasFeature(
features[i][0], features[i][1]));
}
浏览器与后端的差异
// 浏览器: document.implementation
var impl = document.implementation;
// Node.js: 通过库提供
const { DOMImplementation } = require("xmldom");
const impl = new DOMImplementation();
// Python: xml.dom.minidom 中
# from xml.dom.minidom import getDOMImplementation
# impl = getDOMImplementation()
# doc = impl.createDocument(None, "root", None)
使用场景
// 1. 动态生成新的 XML 文档(内存中构建)
// 2. 特性检测(判断 API 可用性)
// 3. 创建带 DOCTYPE 的文档(如 RSS/配置)
// 示例: 内存构建 RSS 文档
var d = doc.implementation.createDocument(
null, "rss", null);
var channel = d.createElement("channel");
d.documentElement.appendChild(channel);
DocumentImpl 是"文档的工厂":创建文档、创建 DOCTYPE、特性检测。日常用得不多,但动态生成 XML 文档时离不开它。
DOM DocumentType
DOM DocumentType
DocumentType 节点代表文档的 DOCTYPE 声明,描述文档遵循的 DTD(文档类型定义)。
什么是 DOCTYPE
<!DOCTYPE bookstore
SYSTEM "bookstore.dtd">
// 或 HTML 中的
<!DOCTYPE html>
DOCTYPE 声明文档类型,部分解析器用它校验文档。
DocumentType 节点表示这个声明。
DocumentType 接口
// 通过 doc.doctype 访问
var doctype = doc.doctype;
doctype.nodeType // 10 (DOCUMENT_TYPE_NODE)
doctype.nodeName // DOCTYPE 名称(如 "bookstore")
doctype.name // 同上
// DTD 标识
doctype.publicId // PUBLIC 标识(如 "-//W3C//DTD XHTML")
doctype.systemId // SYSTEM 标识(如 "bookstore.dtd")
// 内部子集(可选)
doctype.internalSubset // 内部 DTD 声明字符串
示例 XML 与 DOCTYPE
<?xml version="1.0"?>
<!DOCTYPE bookstore SYSTEM "bookstore.dtd">
<bookstore>
<book></book>
</bookstore>
// 解析后
var dt = doc.doctype;
dt.name // "bookstore"
dt.systemId // "bookstore.dtd"
dt.publicId // ""(无 PUBLIC)
判断是否有 DOCTYPE
if (doc.doctype) {
console.log("文档类型: " + doc.doctype.name);
} else {
console.log("无 DOCTYPE 声明");
}
// 注意: 无 DOCTYPE 时 doc.doctype 为 null
创建 DOCTYPE
// 通过 DOMImplementation 创建
var doctype = doc.implementation.createDocumentType(
"bookstore", // qualifiedName
"", // publicId
"bookstore.dtd" // systemId
);
// 用于创建新文档
var d = doc.implementation.createDocument(
null, "bookstore", doctype);
DOCTYPE 与校验
// 有 DOCTYPE 的文档,解析器可做合法性校验
// (取决于解析器是否启用校验)
// 常见 DTD 应用:
// - HTML 文档类型(XHTML 过渡/严格)
// - RSS/Atom 订阅格式
// - SVG、配置文件的格式定义
// 校验失败的解析行为由解析器决定:
// - 严格解析器: 报错
// - 宽容解析器: 警告或忽略
遍历时的位置
// DOCTYPE 位于文档开头(根元素之前)
var children = doc.childNodes;
// children[0] 可能是 XML 声明或 DOCTYPE
// children[1] 通常是根元素
for (var i = 0; i < children.length; i++) {
console.log(children[i].nodeType + ": " +
children[i].nodeName);
}
// 可能输出:
// 10: bookstore (DOCTYPE)
// 1: bookstore (根元素)
DocumentType 节点描述文档的"身份证"。多数场景只需读取 doc.doctype 判断类型,创建新文档时用 createDocumentType 声明 DTD。
DOM ProcessingInst
DOM ProcessingInst
ProcessingInstruction(处理指令)节点用于携带 XML 解析器与应用程序的指令,常见如 xml-stylesheet、xml 声明。
处理指令是什么
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="style.xsl"?>
// 以 <? 开头,?> 结尾
// 格式: <?目标 数据?>
// 目标(target): 指令名(如 xml-stylesheet)
// 数据(data): 指令内容(如 type="text/xsl" href="...")
ProcessingInstruction 接口
// 节点属性
pi.nodeType // 7 (PROCESSING_INSTRUCTION_NODE)
pi.nodeName // 目标名(如 "xml-stylesheet")
pi.target // 同上(目标名)
pi.data // 指令数据(如 type="text/xsl" ...)
// 示例
var pi = doc.firstChild;
if (pi.nodeType === 7) {
console.log("目标: " + pi.target);
console.log("数据: " + pi.data);
}
遍历找到处理指令
// 遍历文档顶层节点
function findPIs(doc) {
var result = [];
var nodes = doc.childNodes;
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].nodeType === 7) {
result.push(nodes[i]);
}
}
return result;
}
var pis = findPIs(doc);
pis.forEach(function(pi) {
console.log(pi.target + " → " + pi.data);
});
创建处理指令
// 创建并插入
var pi = doc.createProcessingInstruction(
"xml-stylesheet",
"type="text/xsl" href="style.xsl""
);
// 插入到文档开头(根元素之前)
doc.insertBefore(pi, doc.documentElement);
// 序列化输出:
// <?xml-stylesheet type="text/xsl" href="style.xsl"?>
XML 声明与处理指令
// 注意: XML 声明(<?xml?>)严格说是处理指令
// 但多数实现中不把它作为可访问的节点
// 实用场景:
// 1. 关联样式表(xml-stylesheet)
// 2. 关联 XSLT 转换
// 3. 传递解析器选项
// 4. 应用特定指令(PHP 的 <?php?> 类似概念)
修改处理指令
// 修改 data
pi.data = "type="text/xsl" href="new.xsl"";
// 删除处理指令
pi.parentNode.removeChild(pi);
// 替换
var newPi = doc.createProcessingInstruction(
"xml-stylesheet", "href="new.xsl"");
pi.parentNode.replaceChild(newPi, pi);
处理指令是 XML 文档中的"元指令"。识别它(nodeType 7)、读取它(target/data)、创建它,即可操控这类特殊节点。
DOM Element
DOM Element
Element(元素)是最重要的 DOM 节点类型,代表 XML 中的标签。绝大多数 DOM 操作都围绕元素展开。
Element 概览
<book id="1" category="web">
<title>Ruby</title>
</book>
var book = doc.getElementsByTagName("book")[0];
book.nodeType // 1 (ELEMENT_NODE)
book.tagName // "book"(等同 nodeName)
book.nodeName // "book"
Element 属性操作
// 读取
book.getAttribute("id") // "1"
book.getAttributeNode("id") // 属性节点
book.hasAttribute("category") // true
book.hasAttributes() // true
book.attributes // NamedNodeMap
// 写入
book.setAttribute("lang", "zh") // 新增/修改
book.removeAttribute("lang") // 删除
book.setAttributeNode(attrNode) // 用节点设置
// 遍历属性
for (var i = 0; i < book.attributes.length; i++) {
var a = book.attributes[i];
console.log(a.nodeName + "=" + a.nodeValue);
}
Element 内容操作
// 文本
title.textContent // "Ruby"(全部文本)
title.firstChild.nodeValue // 第一个文本子节点
// 子元素
book.children // 仅元素子节点
book.childNodes // 所有子节点(含文本)
book.firstElementChild // 第一个子元素
book.lastElementChild // 最后一个子元素
// 结构操作
book.appendChild(newNode)
book.insertBefore(newNode, refNode)
book.removeChild(child)
book.replaceChild(newNode, oldNode)
按标签名搜索子元素
// 从元素开始向下搜索
var titles = book.getElementsByTagName("title");
// 遍历元素(HTML DOM 中的 children)
for (var i = 0; i < book.children.length; i++) {
console.log(book.children[i].nodeName);
}
命名空间相关
<ns:book xmlns:ns="http://example.com/ns">
book.localName // "book"(无前缀名)
book.prefix // "ns"(前缀)
book.namespaceURI // "http://example.com/ns"
// 带命名空间的属性操作
book.getAttributeNS("http://example.com/ns", "id")
book.setAttributeNS(ns, "ns:id", "1")
克隆与替换元素
// 克隆(见克隆章节)
var clone = book.cloneNode(true);
// 替换元素名(无直接 API,重建替换)
function renameElement(elem, newName) {
var doc = elem.ownerDocument;
var newElem = doc.createElement(newName);
// 复制属性
for (var i = 0; i < elem.attributes.length; i++) {
var a = elem.attributes[i];
newElem.setAttribute(a.nodeName, a.nodeValue);
}
// 移动子节点
while (elem.firstChild) {
newElem.appendChild(elem.firstChild);
}
elem.parentNode.replaceChild(newElem, elem);
return newElem;
}
Element 实践模式
// 遍历元素树(仅元素)
function walkElements(elem, depth) {
console.log(" ".repeat(depth) + elem.tagName);
for (var i = 0; i < elem.children.length; i++) {
walkElements(elem.children[i], depth + 1);
}
}
walkElements(doc.documentElement, 0);
Element 是 DOM 编程的主角:属性用 get/setAttribute,内容用 textContent/children,结构用 append/remove。掌握 Element,就掌握了 DOM 的核心。
DOM Attribute
DOM Attribute
Attribute(属性节点)代表 XML 元素上的属性,如 id="1"。属性以 NamedNodeMap 形式挂在元素上,也可独立创建。
Attribute 节点特征
<book id="1">
var attr = book.getAttributeNode("id");
attr.nodeType // 2 (ATTRIBUTE_NODE)
attr.nodeName // "id"(属性名)
attr.nodeValue // "1"(属性值)
attr.name // "id"(等同 nodeName)
attr.value // "1"(等同 nodeValue,现代)
// 属性节点的特殊性:
// - 不属于元素的 childNodes
// - 不能直接 appendChild 添加
// - 通过 attributes/setAttribute 管理
获取属性节点
// 方式1: getAttributeNode
var attr = book.getAttributeNode("id");
// 方式2: 从 NamedNodeMap 中取
var attr2 = book.attributes.getNamedItem("id");
// 方式3: 遍历
for (var i = 0; i < book.attributes.length; i++) {
if (book.attributes[i].nodeName === "id") {
// 找到 id 属性节点
}
}
创建与设置属性节点
// 创建属性节点
var attr = doc.createAttribute("category");
attr.nodeValue = "web";
// 设置到元素
book.setAttributeNode(attr);
// 或
book.attributes.setNamedItem(attr);
// 效果等同
book.setAttribute("category", "web");
修改属性节点
// 修改 nodeValue 会同步到元素
var attr = book.getAttributeNode("id");
attr.nodeValue = "999";
book.getAttribute("id"); // "999" ✓ 已更新
// 部分实现支持 value 属性
attr.value = "888";
// 注意: 修改 name 可能不受支持(属性名不可改)
// 改属性名 = 删除旧 + 新建
删除属性节点
// 方式1
book.removeAttributeNode(attrNode);
// 方式2
book.attributes.removeNamedItem("id");
// 方式3
book.removeAttribute("id");
属性 vs 元素对比
| 维度 | 属性(Attribute) | 元素(Element) |
|------------|---------------------|----------------------|
| 节点类型 | 2 | 1 |
| 内容 | 简单字符串值 | 可嵌套子节点 |
| 遍历 | 不在 childNodes | 在 childNodes |
| 顺序 | 不保证顺序 | 文档顺序 |
| 用途 | 元数据/简单值 | 结构化内容 |
// 设计建议: 简单标量用属性,复杂结构用元素
// <price currency="CNY">59</price>
// 属性=货币单位, 元素=价格数值
实践示例
// 读取所有属性的键值
function getAttrMap(elem) {
var map = {};
var attrs = elem.attributes;
for (var i = 0; i < attrs.length; i++) {
map[attrs[i].nodeName] = attrs[i].nodeValue;
}
return map;
}
var attrs = getAttrMap(book);
// { id: "1", category: "web" }
Attribute 节点代表"元素的属性"。日常用 setAttribute/getAttribute 便捷方法,需要遍历或节点级操作时才直接接触 Attribute 节点。
DOM Text
DOM Text
Text(文本节点)代表元素中的文本内容。文本节点是叶子节点,没有子节点。
Text 节点特征
<title>Ruby 入门</title>
var title = doc.getElementsByTagName("title")[0];
var textNode = title.firstChild;
textNode.nodeType // 3 (TEXT_NODE)
textNode.nodeName // "#text"
textNode.nodeValue // "Ruby 入门"
textNode.data // "Ruby 入门"(等价,推荐)
textNode.length // 文本长度
// 文本节点没有子节点
textNode.hasChildNodes() // false
读取文本
// 通过文本节点
var t = title.firstChild;
t.nodeValue // "Ruby 入门"
t.data // "Ruby 入门"
// 便捷方式(推荐)
title.textContent // "Ruby 入门"
// 注意: 元素可能包含多个文本节点
// 由多次 appendChild 或实体引起
// normalize() 可合并
修改文本
// 修改文本节点值
var t = title.firstChild;
t.nodeValue = "新标题";
t.data = "新标题"; // 等价
// 或用 textContent 直接改
title.textContent = "新标题";
// 拆分文本节点
var first = t.splitText(3); // 前3字符为第一个节点
// t: "新标" first: "题"
创建与追加文本
// 创建文本节点
var text = doc.createTextNode("Ruby 高级教程");
title.appendChild(text);
// 追加到已有文本
var t = title.firstChild;
t.appendData(" - 第二版"); // 末尾追加
t.insertData(5, "!!"); // 指定位置插入
// 常用 data 操作方法:
t.appendData(str) // 追加
t.insertData(offset, str) // 插入
t.deleteData(offset, count) // 删除
t.replaceData(offset, count, str) // 替换
t.substringData(offset, count) // 提取
文本节点的空白问题
<book>
<title>Ruby</title>
</book>
// 格式化后,book 的子节点包含:
// - 文本节点(换行与空格)
// - title 元素
// - 文本节点(换行与空格)
// 处理: 遍历时过滤
function getDirectText(elem) {
var result = "";
for (var i = 0; i < elem.childNodes.length; i++) {
var n = elem.childNodes[i];
if (n.nodeType === 3) {
result += n.data;
}
}
return result.trim();
}
CDATA 与 Text 的区别
// CDATA 段在 XML 中写法不同,但读取方式相似
<script><![CDATA[ if (a < b) {...} ]]></script>
// 遍历时:
// CDATA 节点 nodeType = 4
// 文本节点 nodeType = 3
// 处理时通常统一对待(都取 data/textContent)
Text 节点是内容的最终承载者。读取用 textContent 最省事,精细操作(拆分/插入/提取)才需要直接使用 Text 接口。
DOM CDATA
DOM CDATA
CDATA 段(Character Data)用于在 XML 中存放不需要转义的特殊字符,如代码片段。DOM 用 CDATASection 接口表示。
什么是 CDATA
<![CDATA[ 内容 ]]>
// 示例
<code><![CDATA[
if (a < b && c > d) { ... }
]]></code>
// CDATA 中的 < & 等字符无需转义
// 普通文本节点中则必须写成 < &
CDATA 节点特征
// 解析后成为 CDATASection 节点
var cdata = codeElem.firstChild;
cdata.nodeType // 4 (CDATA_SECTION_NODE)
cdata.nodeName // "#cdata-section"
cdata.nodeValue // "if (a < b ...)"
cdata.data // 内容(推荐)
// CDATASection 继承自 Text
// 所以 Text 的方法都可用
创建 CDATA
// 创建 CDATA 段
var cdata = doc.createCDATASection(
"if (a < b) { return; }");
codeElem.appendChild(cdata);
// 序列化输出:
// <![CDATA[if (a < b) { return; }]]>
读取 CDATA 内容
// 方式1: 通过 data
var content = cdata.data;
// 方式2: 通过 textContent(父元素)
var content = codeElem.textContent;
// 方式3: 遍历所有子节点,找 CDATA
function getCDATAs(elem) {
var result = [];
for (var i = 0; i < elem.childNodes.length; i++) {
if (elem.childNodes[i].nodeType === 4) {
result.push(elem.childNodes[i].data);
}
}
return result;
}
CDATA vs 转义文本
// 两种方式存储相同内容:
// CDATA 方式
<code><![CDATA[a < b]]></code>
// 转义方式
<code>a < b</code>
// 读取结果一致("a < b")
// 选择建议:
// - 含大量特殊字符(代码/脚本): CDATA
// - 少量特殊字符: 转义更简洁
// - 注意: CDATA 内不能包含 "]]>" 序列
修改与删除 CDATA
// 修改内容
cdata.data = "新内容"; // 直接改 data
cdata.nodeValue = "新内容";
// 替换为普通文本节点
var text = doc.createTextNode("普通文本");
codeElem.replaceChild(text, cdata);
// 删除
codeElem.removeChild(cdata);
// 拆分(继承自 Text)
var part1 = cdata.splitText(5);
CDATA 遍历注意
// 遍历时 CDATA 与 Text 常需一起处理
function getTextContent(elem) {
var result = "";
for (var i = 0; i < elem.childNodes.length; i++) {
var n = elem.childNodes[i];
if (n.nodeType === 3 || n.nodeType === 4) {
result += n.data;
}
}
return result;
}
// 通常 textContent 已自动处理两者
CDATA 是 XML 中存放"原始内容"的机制。创建用 createCDATASection,读取用 data/textContent,注意"]]>"不能出现在 CDATA 内部。
DOM Comment
DOM Comment
Comment(注释节点)代表 XML/HTML 中的注释,用于在文档中加入说明文字,注释不会显示给最终用户。
注释与节点
<!-- 这是注释 -->
var comment = doc.createComment("这是注释");
comment.nodeType // 8 (COMMENT_NODE)
comment.nodeName // "#comment"
comment.nodeValue // "这是注释"
comment.data // "这是注释"(推荐)
遍历发现注释
// 找到所有注释
function findComments(node, result) {
if (node.nodeType === 8) {
result.push(node.data);
}
for (var i = 0; i < node.childNodes.length; i++) {
findComments(node.childNodes[i], result);
}
return result;
}
var comments = findComments(doc.documentElement, []);
// ["这是注释1", "这是注释2"]
创建与插入注释
// 创建
var comment = doc.createComment("记录修改原因");
// 插入到元素前
var book = doc.getElementsByTagName("book")[0];
book.parentNode.insertBefore(comment, book);
// 插入到元素后
book.parentNode.insertBefore(
comment, book.nextSibling);
// 添加为子节点
elem.appendChild(doc.createComment("子注释"));
修改与删除注释
// 修改注释内容
comment.data = "新注释内容";
comment.nodeValue = "新注释内容";
// 删除
comment.parentNode.removeChild(comment);
// 替换
var newComment = doc.createComment("替换后的注释");
comment.parentNode.replaceChild(newComment, comment);
注释的实际用途
// 1. 代码说明: 复杂逻辑的解释
// 2. 区域标记: 便于定位大段结构
// 3. 临时禁用: 注释掉暂时不用的内容
// 4. 元信息: 版本、作者、生成时间
// 5. 配置开关: 部分解析器支持读取注释配置
// 注意: 注释可能包含敏感信息(如生成路径)
// 对外发布前清理
注释与空白
// 格式化 XML 中,注释与空白文本共存
// 遍历时需要同时过滤
function forEachRealNode(elem, fn) {
for (var i = 0; i < elem.childNodes.length; i++) {
var n = elem.childNodes[i];
if (n.nodeType === 1) { // 元素
fn(n);
}
// nodeType 3(文本)/8(注释)可忽略
}
}
注释节点用于"记录说明"。识别(nodeType 8)、读取(data)、创建(createComment)三个操作足够日常使用。
DOM ParseError Obj
DOM ParseError Obj
解析错误对象(ParseError)用于描述 XML 解析失败的信息。不同解析器暴露错误的方式不同,本节介绍常见模式。
浏览器中的解析错误
// 浏览器 DOMParser: 解析错误时生成
// 元素
var parser = new DOMParser();
var doc = parser.parseFromString(badXml, "text/xml");
// 检测错误
var errors = doc.getElementsByTagName("parsererror");
if (errors.length > 0) {
console.log("解析失败:");
console.log(errors[0].textContent);
}
parsererror 内容示例
<parsererror xmlns="http://www.mozilla.org/newlayout/xml/parsererror.xml">
文档解析错误
位置: line 3, column 5
错误: 元素类型 "book" 后必须跟属性规范或 "/>"
</parsererror>
// 提取信息
var errText = errors[0].textContent;
// 可解析出行号与错误描述
XHR 加载错误检测
var xhr = new XMLHttpRequest();
xhr.open("GET", "data.xml", false);
xhr.send();
if (xhr.status !== 200) {
console.log("HTTP 错误: " + xhr.status);
} else {
var doc = xhr.responseXML;
// responseXML 为 null 时说明解析失败
if (doc === null) {
console.log("XML 解析失败(响应非合法 XML)");
} else {
console.log("解析成功");
}
}
Python 中的解析错误
from xml.dom import minidom
from xml.parsers.expat import ExpatError
try:
doc = minidom.parseString(bad_xml)
except ExpatError as e:
print(f"解析错误: {e}")
print(f"行: {e.lineno}, 列: {e.offset}")
print(f"错误码: {e.code}")
# 输出示例:
# 解析错误: mismatched tag: line 3, column 5
Node.js 中的解析错误
const { DOMParser } = require("xmldom");
const fs = require("fs");
const xml = fs.readFileSync("data.xml", "utf-8");
try {
const doc = new DOMParser({
locator: {},
errorHandler: {
warning: console.warn,
error: console.error, // 解析错误
fatalError: console.error
}
}).parseFromString(xml, "text/xml");
} catch (e) {
console.error("解析失败:", e);
}
解析错误处理最佳实践
// 1. 解析前校验(正则/预处理)
// 2. 解析后立即检测错误
// 3. 错误信息中包含行号与描述
// 4. 优雅降级: 提示用户而非崩溃
// 5. 记录错误场景便于排查
// 统一错误处理函数
function safeParse(xmlText) {
var doc = parser.parseFromString(xmlText, "text/xml");
var err = doc.getElementsByTagName("parsererror");
if (err.length > 0) {
throw new Error("XML 解析错误: " +
err[0].textContent);
}
return doc;
}
解析错误处理是健壮 XML 应用的必要环节:检测失败、提取信息、友好提示。不同环境用各自方式,但"检测-提取-提示"的思路一致。
DOM 解析器错误
DOM 解析器错误
本节汇总解析器常见错误类型、原因与排查方法,帮助你快速定位 XML 问题。
常见错误分类
1. 语法错误: XML 格式不合法
2. 结构错误: 标签不匹配/嵌套错误
3. 编码错误: 字符编码问题
4. 校验错误: 不符合 DTD/Schema
5. 资源错误: 引用的外部实体不可访问
常见错误与原因
# 错误: mismatched tag (标签不匹配)
<book><title>...</book> ← 缺 </title>
# 错误: not well-formed (格式不正确)
<book id=1> ← 属性必须加引号
<book></Book> ← 大小写不一致
# 错误: invalid character (非法字符)
文本中含有控制字符或未转义 &
# 错误: encoding error (编码错误)
文件声明 UTF-8 但实际是 GBK 等
# 错误: entity not defined (实体未定义)
使用了未声明的实体 &foo;
快速排查清单
# 按顺序检查:
# 1. 标签配对: 每个开始标签都有结束标签
# 2. 大小写: XML 严格区分大小写
# 3. 引号: 属性值必须用引号包裹
# 4. 特殊字符: < & 等必须转义
# 5. 嵌套: 标签必须正确嵌套(不能交错)
# 6. 编码: 声明与实际编码一致
# 7. 根元素: 有且只有一个根元素
# 8. 注释: <!-- 和 --> 配对
# 9. CDATA: 内部不能有 ]]>
# 10. DOCTYPE/实体: 声明正确
定位错误位置
// 解析错误通常包含行号/列号
// 用行号定位到具体位置
function locateErrorLine(xmlText, lineNo) {
var lines = xmlText.split("
");
if (lineNo >= 1 && lineNo <= lines.length) {
return lines[lineNo - 1];
}
return "";
}
// 示例: 第三行出错
var badLine = locateErrorLine(xmlText, 3);
console.log("出错行: " + badLine);
转义规则速查
XML 中必须转义的字符:
< → <
> → >(通常可省略,但建议转义)
& → &
" → "(属性值中)
' → '(属性值中)
// 示例
<title>5 < 10 && ok</title>
// 解析结果: 5 < 10 && ok
编码问题处理
// 声明与实际编码一致
<?xml version="1.0" encoding="UTF-8"?>
// 文件另存为 UTF-8(无 BOM 最稳妥)
// Python 读取时指定编码
open("data.xml", encoding="utf-8").read()
// 浏览器 XHR 加载:
// 服务端返回正确的 Content-Type
// 或 xhr.overrideMimeType("text/xml; charset=utf-8")
工具推荐
# 在线/本地 XML 校验工具
# - W3C XML Validator
# - xmllint(Linux 命令行)
xmllint --noout data.xml
# 输出: data.xml:3: parser error : ...
# 编辑器插件实时校验
# VS Code 的 XML 插件
解析器错误绝大多数是"格式问题"。按排查清单逐项检查,配合行号定位与校验工具,通常几分钟就能找到并修复。
DOM 总结
DOM 总结
本节回顾 XML DOM 教程的核心知识点,帮助你建立完整的知识体系。
核心概念回顾
# 1. 节点树
# DOM 把 XML 表示为节点树:
# Document → 根元素 → 子元素 → 文本
# 2. 节点类型
# 1 元素 / 2 属性 / 3 文本 / 4 CDATA
# 8 注释 / 9 文档 / 10 DOCTYPE / 7 处理指令
# 3. 节点关系
# parentNode / childNodes / firstChild
# lastChild / nextSibling / previousSibling
核心操作回顾
# 加载
DOMParser.parseFromString / XHR.responseXML
# 查询
getElementsByTagName / getAttribute
textContent / attributes
# 修改
textContent / setAttribute / removeAttribute
# 创建
createElement / createTextNode / createAttribute
# 结构
appendChild / insertBefore / removeChild
replaceChild / cloneNode / normalize
常用接口一览
# 文档级
Document: documentElement, createElement, ...
DOMImplementation: createDocument, hasFeature
# 节点级(通用)
Node: nodeName/nodeValue/nodeType,
parentNode/childNodes, appendChild...
# 元素级
Element: tagName, get/setAttribute, attributes
# 集合
NodeList: length, item
NamedNodeMap: getNamedItem, setNamedItem
# 内容
Text: data, splitText
CDATASection / Comment / ProcessingInstruction
实践技巧总结
# 1. 空白节点: 格式化 XML 会产生空白文本节点
# → 用 children / nextElementSibling 过滤
# 2. 活集合: getElementsByTagName 返回活集合
# → 删除时反向遍历或先转快照
# 3. 文本读取: 优先 textContent
# 4. 创建节点: 必须用所属文档的 create* 方法
# 5. 解析错误: 检测 parsererror / responseXML 为 null
应用场景
# XML DOM 的应用:
# - 解析配置文件与数据文件
# - 前后端数据交换(XML 格式)
# - 文档转换(XSLT 配合)
# - 网页数据抓取(HTML DOM 同源)
# 前端 HTML 场景使用更丰富的:
# HTML DOM(document.getElementById 等)
学习路线建议
# 已掌握: XML DOM 节点模型与操作
# 下一步可选:
# - HTML DOM: 网页操作(与 XML DOM 同源)
# - XSLT: XML 转换样式表
# - XPath: 更强大的节点查询
# - 语言库: Python lxml / Java DOM 等
XML DOM 的核心就一句话:文档是树,一切皆节点,操作靠方法。把这棵树的遍历、查询、修改练熟,XML 处理就不再是难题。
DOM 实例
DOM 实例
本节通过 5 个完整实例,综合运用 XML DOM 的解析、查询、修改、创建与序列化能力。
实例1: 解析并显示书籍列表
// books.xml
// <bookstore><book id="1"><title>Ruby</title><price>59</price></book>...</bookstore>
function loadXML(filename) {
var xhr = new XMLHttpRequest();
xhr.open("GET", filename, false);
xhr.send();
return xhr.responseXML;
}
var doc = loadXML("books.xml");
var books = doc.getElementsByTagName("book");
var html = "<table>";
html += "<tr><th>ID</th><th>书名</th><th>价格</th></tr>";
for (var i = 0; i < books.length; i++) {
var b = books[i];
html += "<tr>";
html += "<td>" + b.getAttribute("id") + "</td>";
html += "<td>" + b.getElementsByTagName("title")[0]
.textContent + "</td>";
html += "<td>" + b.getElementsByTagName("price")[0]
.textContent + "</td>";
html += "</tr>";
}
html += "</table>";
document.getElementById("result").innerHTML = html;
实例2: 新增一本书
function addBook(id, titleText, priceText) {
var doc = loadXML("books.xml");
var root = doc.documentElement;
// 创建 book
var book = doc.createElement("book");
book.setAttribute("id", id);
var title = doc.createElement("title");
title.textContent = titleText;
book.appendChild(title);
var price = doc.createElement("price");
price.textContent = priceText;
book.appendChild(price);
root.appendChild(book);
return doc;
}
// 用 XMLSerializer 输出新文档
实例3: 搜索书籍
function searchBooks(keyword) {
var doc = loadXML("books.xml");
var books = doc.getElementsByTagName("book");
var result = [];
for (var i = 0; i < books.length; i++) {
var title = books[i]
.getElementsByTagName("title")[0]
.textContent;
if (title.indexOf(keyword) !== -1) {
result.push({
id: books[i].getAttribute("id"),
title: title
});
}
}
return result;
}
// searchBooks("Ruby") → [{id:"1", title:"Ruby 入门"}]
实例4: 修改价格
function updatePrice(id, newPrice) {
var doc = loadXML("books.xml");
var books = doc.getElementsByTagName("book");
for (var i = 0; i < books.length; i++) {
if (books[i].getAttribute("id") === id) {
var price = books[i]
.getElementsByTagName("price")[0];
price.textContent = newPrice;
return doc; // 返回修改后的文档
}
}
return null; // 未找到
}
实例5: 遍历并统计
// 统计每个作者的书籍数量
function countByAuthor(doc) {
var books = doc.getElementsByTagName("book");
var stats = {};
for (var i = 0; i < books.length; i++) {
var author = books[i]
.getElementsByTagName("author")[0]
.textContent;
stats[author] = (stats[author] || 0) + 1;
}
return stats;
}
// { "张三": 3, "李四": 2 }
实例要点
# 五个实例覆盖:
# - 解析加载(loadXML)
# - 查询(getElementsByTagName + getAttribute)
# - 创建组装(createElement + appendChild)
# - 修改(textContent)
# - 遍历统计(循环 + 对象聚合)
# 综合运用这些模式,
# 就能实现绝大多数 XML 数据处理需求
实例是最好的学习方式。动手运行一遍,把每个实例改造成自己的需求,XML DOM 的使用就真正上手了。
DOM 验证
DOM 验证
文档验证(Validation)是检查 XML 是否符合规则的过程:语法合法(well-formed)与结构合法(valid,符合 DTD/Schema)。
Well-formed vs Valid
# Well-formed(格式良好): 语法正确
# - 标签配对、大小写一致、正确嵌套
# - 属性带引号、特殊字符转义
# - 有唯一根元素
# 解析器基本都会检查这一层
# Valid(有效): 结构符合 DTD/Schema
# - 元素顺序、必需元素、属性约束
# - 数据类型、取值范围
# 需要校验器(validator)检查
DTD 校验
<!DOCTYPE bookstore [
<!ELEMENT bookstore (book+)>
<!ELEMENT book (title, price)>
<!ELEMENT title (#PCDATA)>
<!ELEMENT price (#PCDATA)>
<!ATTLIST book id CDATA #REQUIRED>
]>
// 校验规则:
// - bookstore 必须有一个或多个 book
// - book 必须含 title 和 price(按顺序)
// - book 必须有 id 属性
// 校验工具(xmllint):
xmllint --valid --noout data.xml
Schema 校验(XSD)
// XSD 更强大,支持数据类型与命名空间
// books.xsd
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="book">
<xs:complexType>
<xs:sequence>
<xs:element name="title" type="xs:string"/>
<xs:element name="price" type="xs:decimal"/>
</xs:sequence>
<xs:attribute name="id" type="xs:ID" use="required"/>
</xs:complexType>
</xs:element>
</xs:schema>
// Python 校验示例
from lxml import etree
schema = etree.XMLSchema(etree.parse("books.xsd"))
doc = etree.parse("data.xml")
print(schema.validate(doc)) # True/False
DOM 操作后的验证
// 程序修改文档后,可用校验器复查
// 场景: 增删改后确保结构仍合法
// 浏览器端(部分环境):
// 无内置 XSD 校验,通常服务端校验
// 前端常见做法:
// 1. 修改后检查关键结构(必备元素)
function checkBook(book) {
var ok = true;
ok = ok && book.hasAttribute("id");
ok = ok && book.getElementsByTagName("title").length === 1;
ok = ok && book.getElementsByTagName("price").length === 1;
return ok;
}
// 2. 关键数据校验
function checkPrice(price) {
var v = parseFloat(price.textContent);
return !isNaN(v) && v >= 0;
}
验证实践建议
# 1. 数据入口验证: 外部 XML 先验证再入库
# 2. 生成输出验证: 序列化后跑一遍校验器
# 3. 增量验证: 修改后只验改动部分
# 4. 自动化: CI 中加入 XML 校验步骤
# CI 校验示例
xmllint --noout $(find . -name "*.xml")
xmllint --schema books.xsd --noout data.xml
验证与解析的关系
# 解析器: 检查 well-formed(自动)
# 校验器: 检查 valid(可选,需显式调用)
# 流程: 先解析(语法),后校验(结构)
# 没有 DTD/Schema 时:
# 只能保证 well-formed,无法保证 valid
# 靠程序逻辑在代码中做校验(如 checkBook)
验证是 XML 质量的保障:格式良好是底线,结构有效是要求。有 DTD/Schema 用工具校验,没有就在代码里写检查函数。
XML DOM 简介
XML DOM 简介
欢迎学习 XML DOM!本节作为课程的收尾,回顾 XML DOM 的定位,并给出学习后的行动建议。
XML DOM 是什么(回顾)
XML DOM = XML Document Object Model
= 操作 XML 文档的标准编程接口
核心思想:
- 文档 → 节点树
- 一切内容都是节点
- 通过标准接口遍历/查询/修改
学完本教程,你应该掌握
# 概念层
✓ 节点与节点树模型
✓ 12 种节点类型
✓ 节点之间的关系
# 操作层
✓ 解析 XML(DOMParser/XHR)
✓ 查询节点(getElementsByTagName 等)
✓ 读取内容(textContent/attributes)
✓ 修改节点(文本/属性/结构)
✓ 创建与插入节点
✓ 删除/替换/克隆节点
# 对象层
✓ Node / NodeList / NamedNodeMap
✓ Element / Attribute / Text
✓ Document / DocumentType / Comment
✓ CDATASection / ProcessingInstruction
# 实践层
✓ 错误处理与验证
✓ 实例应用(书籍列表/增删改查)
XML DOM 与 HTML DOM 的关系
HTML DOM = XML DOM + HTML 专属扩展
共同点:
- 节点树模型
- Node/Element/Text 等接口
- 遍历与修改方法
HTML 专属(更便捷):
- document.getElementById/querySelector
- document.forms/images/links
- 事件处理、样式操作
// 学了 XML DOM,HTML DOM 会非常容易上手
实践建议
# 1. 动手做: 把"DOM 实例"章节的代码跑起来
# 2. 扩展练: 做一个 XML 配置读取器
# 3. 进阶学: XPath 查询 / XSLT 转换
# 4. 换语言: Python lxml / Java DOM 对比学习
# 5. 应用: 前后端 XML 数据交互实战
常见场景提醒
# 实际开发中的选择:
# - 简单配置: 用 JSON/YAML 更简单(推荐)
# - 复杂文档/标准格式: XML + DOM/Schema
# - 网页操作: HTML DOM
# - 高性能解析: SAX / StAX(流式)或 lxml
# 选型原则: 适合场景的技术才是好技术
XML DOM 是文档编程的基础功,理解它,你不仅能处理 XML,也能更快掌握 HTML DOM 与各种语言中的 XML 库。现在动手写几个例子,巩固你的理解吧!