文章目录
1 DOM 的演进
1.1 XML 命名空间
XML 命名空间可以实现在一个格式规范的文档中混用不同的 XML 语言,而不必担心元素命名冲突。严格来讲,XML 命名空间在 XHTML 中才支持,HTML 并不支持。因此,本节的示例使用 XHTML。
下面这个文档就使用了 XHTML和 SVG 两种语言:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Example XHTML page</title>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
viewBox="0 0 100 100" style="width:100%; height:100%">
<rect x="0" y="0" width="100" height="100" style="fill:red" />
</svg>
</body>
</html>
在这个例子中,通过给svg元素设置自己的命名空间,将其标识为当前文档的外来元素。这样一
来,svg元素及其属性,包括它的所有后代都会被认为属于"https://www.w3.org/2000/svg"命名空间。虽然这个文档从技术角度讲是 XHTML 文档,但由于使用了命名空间,其中包含的 SVG 代码也是有效的。
对于这样的文档,如果调用某个方法与节点交互,就会出现一个问题。比如,创建了一个新元素,那这个元素属于哪个命名空间?查询特定标签名时,结果中应该包含哪个命名空间下的元素?DOM2 Core 为解决这些问题,给大部分 DOM1 方法提供了特定于命名空间的版本。
1. Node 的变化
在 DOM2 中,Node 类型包含以下特定于命名空间的属性:
localName,不包含命名空间前缀的节点名;
namespaceURI,节点的命名空间 URL,如果未指定则为 null;
prefix,命名空间前缀,如果未指定则为 null。
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Example XHTML page</title>
</head>
<body>
<s:svg xmlns:s="http://www.w3.org/2000/svg" version="1.1"
viewBox="0 0 100 100" style="width:100%; height:100%">
<s:rect x="0" y="0" width="100" height="100" style="fill:red" />
</s:svg>
</body>
</html>
其中的元素的 localName 和 tagName 都是"html",namespaceURL 是"http://www.w3.
org/1999/xhtml",而 prefix 是 null。对于<s:svg>元素,localName 是"svg",tagName 是
“s:svg”,namespaceURI 是"https://www.w3.org/2000/svg",而 prefix 是"s"。
DOM3 进一步增加了如下与命名空间相关的方法:
isDefaultNamespace(namespaceURI),返回布尔值,表示 namespaceURI 是否为节点的默
认命名空间;
lookupNamespaceURI(prefix),返回给定 prefix 的命名空间 URI;
lookupPrefix(namespaceURI),返回给定 namespaceURI 的前缀。
对前面的例子,可以执行以下代码:
console.log(document.body.isDefaultNamespace("http://www.w3.org/1999/
xhtml")); // true
// 假设 svg 包含对<s:svg>元素的引用
console.log(svg.lookupPrefix("http://www.w3.org/2000/svg")); // "s"
console.log(svg.lookupNamespaceURI("s")); // "http://www.w3.org/2000/svg"
2. Document 的变化
DOM2 在 Document 类型上新增了如下命名空间特定的方法:
createElementNS(namespaceURI, tagName),以给定的标签名 tagName 创建指定命名空
间 namespaceURI 的一个新元素;
createAttributeNS(namespaceURI, attributeName),以给定的属性名 attributeName
创建指定命名空间 namespaceURI 的一个新属性;
getElementsByTagNameNS(namespaceURI, tagName),返回指定命名空间 namespaceURI
中所有标签名为 tagName 的元素的 NodeList。
// 创建一个新 SVG 元素
let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
// 创建一个任意命名空间的新属性
let att = document.createAttributeNS("http://www.somewhere.com", "random");
// 获取所有 XHTML 元素
let elems = document.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "*");
3. Element 的变化
getAttributeNS(namespaceURI, localName),取得指定命名空间 namespaceURI 中名为
localName 的属性;
getAttributeNodeNS(namespaceURI, localName),取得指定命名空间 namespaceURI 中
名为 localName 的属性节点;
getElementsByTagNameNS(namespaceURI, tagName),取得指定命名空间 namespaceURI
中标签名为 tagName 的元素的 NodeList;
hasAttributeNS(namespaceURI, localName),返回布尔值,表示元素中是否有命名空间
namespaceURI 下名为 localName 的属性(注意,DOM2 Core 也添加不带命名空间的
hasAttribute()方法);
removeAttributeNS(namespaceURI, localName),删除指定命名空间 namespaceURI 中
名为 localName 的属性;
setAttributeNS(namespaceURI, qualifiedName, value),设置指定命名空间 namespaceURI
中名为 qualifiedName 的属性为 value;
setAttributeNodeNS(attNode),为元素设置(添加)包含命名空间信息的属性节点 attNode。
4. NamedNodeMap 的变化
getNamedItemNS(namespaceURI, localName),取得指定命名空间 namespaceURI 中名为
localName 的项;
removeNamedItemNS(namespaceURI, localName),删除指定命名空间 namespaceURI 中
名为 localName 的项;
setNamedItemNS(node),为元素设置(添加)包含命名空间信息的节点。
这些方法很少使用,因为通常都是使用元素来访问属性。
1.2 其他变化
2 样式
2.1 存取元素样式
在标准模式下,所有尺寸都必须包含单位。在混杂模式下,可以把 style.width设置为"20",相当于"20px"。如果是在标准模式下,把 style.width 设置为"20"会被忽略,因为没有单位。实践中,最好一直加上单位。
1. DOM 样式属性和方法
DOM2 Style 规范也在 style 对象上定义了一些属性和方法。这些属性和方法提供了元素 style 属性的信息并支持修改,列举如下。
cssText,包含 style 属性中的 CSS 代码。
length,应用给元素的 CSS 属性数量。
parentRule,表示 CSS 信息的 CSSRule 对象(下一节会讨论 CSSRule 类型)。
getPropertyCSSValue(propertyName),返回包含 CSS 属性 propertyName 值的 CSSValue对象(已废弃)。
getPropertyPriority(propertyName),如果 CSS 属性 propertyName 使用了!important则返回"important",否则返回空字符串。
getPropertyValue(propertyName),返回属性 propertyName 的字符串值。
item(index),返回索引为 index 的 CSS 属性名。
removeProperty(propertyName),从样式中删除 CSS 属性 propertyName。
setProperty(propertyName, value, priority),设置 CSS 属性 propertyName 的值为value,priority 是"important"或空字符串。
2. 计算样式
DOM2 Style在document.defaultView上增加了getComputedStyle()方法。这个方法接收两个参数:要取得计算样式的元素和伪元素字符串(如":after")。如果不需要查询伪元素,则第二个参数可以传 null。getComputedStyle()方法返回一个 CSSStyleDeclaration对象(与 style 属性的类型一样),包含元素的计算样式。是只读的,不能修改 getComputedStyle()方法返回的对象。
2.2 操作样式表
CSSStyleSheet 类型表示 CSS 样式表,包括使用link元素和通过style元素定义的样式表。这两个元素本身分别是 HTMLLinkElement 和 HTMLStyleElement。CSSStyleSheet 类型的实例则是一个只读对象(只有一个属性例外)。
disabled,布尔值,表示样式表是否被禁用了(这个属性是可读写的,因此将它设置为 true
会禁用样式表)。
href,如果是使用包含的样式表,则返回样式表的 URL,否则返回 null。
media,样式表支持的媒体类型集合,这个集合有一个 length 属性和一个 item()方法,跟所
有 DOM 集合一样。同样跟所有 DOM 集合一样,也可以使用中括号访问集合中特定的项。如果
样式表可用于所有媒体,则返回空列表。
ownerNode,指向拥有当前样式表的节点,在 HTML 中要么是link元素要么是style元素
(在 XML 中可以是处理指令)。如果当前样式表是通过@import 被包含在另一个样式表中,则这
个属性值为 null。
parentStyleSheet,如果当前样式表是通过@import 被包含在另一个样式表中,则这个属性
指向导入它的样式表。
title,ownerNode 的 title 属性。
type,字符串,表示样式表的类型。对 CSS 样式表来说,就是"text/css"。
上述属性里除了 disabled,其他属性都是只读的。除了上面继承的属性,CSSStyleSheet 类型
还支持以下属性和方法。
cssRules,当前样式表包含的样式规则的集合。
ownerRule,如果样式表是使用@import 导入的,则指向导入规则;否则为 null。
deleteRule(index),在指定位置删除 cssRules 中的规则。
insertRule(rule, index),在指定位置向 cssRules 中插入规则。
2.3 元素尺寸
1. 偏移尺寸
offsetHeight
offsetLeft
offsetTop
offsetWidth
2. 客户端尺寸
clientWidth 和 clientHeight。
3. 滚动尺寸
scrollHeight,没有滚动条出现时,元素内容的总高度。
scrollLeft,内容区左侧隐藏的像素数,设置这个属性可以改变元素的滚动位置。
scrollTop,内容区顶部隐藏的像素数,设置这个属性可以改变元素的滚动位置。
scrollWidth,没有滚动条出现时,元素内容的总宽度。
4. 确定元素尺寸
浏览器在每个元素上都暴露了 getBoundingClientRect()方法,返回一个 DOMRect 对象,包含6 个属性:left、top、right、bottom、height 和 width。
3 遍历
NodeIterator 和 TreeWalker——从某个起点开始执行对 DOM 结构的深度优先遍历。
3.1 NodeIterator
可以通过 document.createNodeIterator()方法创建其实例。这个方法接收以下 4 个参数。
root,作为遍历根节点的节点。
whatToShow,数值代码,表示应该访问哪些节点。
filter,NodeFilter 对象或函数,表示是否接收或跳过特定节点。
entityReferenceExpansion,布尔值,表示是否扩展实体引用。这个参数在 HTML 文档中没有效果,因为实体引用永远不扩展。
以下代码定义了只接收p元素的节点过滤器对象:
let filter = {
acceptNode(node) {
return node.tagName.toLowerCase() == "p" ?
NodeFilter.FILTER_ACCEPT :
NodeFilter.FILTER_SKIP;
}
};
let iterator = document.createNodeIterator(root, NodeFilter.SHOW_ELEMENT,
filter, false);
NodeIterator 的两个主要方法是 nextNode()和 previousNode()。nextNode()方法在 DOM
子树中以深度优先方式进前一步,而 previousNode()则是在遍历中后退一步。创建 NodeIterator
对象的时候,会有一个内部指针指向根节点,因此第一次调用 nextNode()返回的是根节点。当遍历到
达 DOM 树最后一个节点时,nextNode()返回 null。previousNode()方法也是类似的。当遍历到达
DOM 树最后一个节点时,调用 previousNode()返回遍历的根节点后,再次调用也会返回 null。
3.2 TreeWalker
TreeWalker 是 NodeIterator 的高级版。除了包含同样的 nextNode()、previousNode()方法,TreeWalker 还添加了如下在 DOM 结构中向不同方向遍历的方法。
parentNode(),遍历到当前节点的父节点。
firstChild(),遍历到当前节点的第一个子节点。
lastChild(),遍历到当前节点的最后一个子节点。
nextSibling(),遍历到当前节点的下一个同胞节点。
previousSibling(),遍历到当前节点的上一个同胞节点。
4 范围
为了支持对页面更细致的控制,DOM2 Traversal and Range 模块定义了范围接口。范围可用于在文
档中选择内容,而不用考虑节点之间的界限。(选择在后台发生,用户是看不到的。)范围在常规 DOM操作的粒度不够时可以发挥作用。
4.1 DOM 范围
let range = document.createRange();
每个范围都是 Range 类型的实例,拥有相应的属性和方法。下面的属性提供了与范围在文档中位置相关的信息。
startContainer,范围起点所在的节点(选区中第一个子节点的父节点)。
startOffset,范围起点在 startContainer 中的偏移量。如果 startContainer 是文本节
点、注释节点或 CData 区块节点,则 startOffset 指范围起点之前跳过的字符数;否则,表示
范围中第一个节点的索引。
endContainer,范围终点所在的节点(选区中最后一个子节点的父节点)。
endOffset,范围起点在 startContainer 中的偏移量(与 startOffset 中偏移量的含义相同)。
commonAncestorContainer,文档中以startContainer和endContainer为后代的最深的节点。
这些属性会在范围被放到文档中特定位置时获得相应的值。
4.2 简单选择
通过范围选择文档中某个部分最简单的方式,就是使用 selectNode()或 selectNodeContents()方法。这两个方法都接收一个节点作为参数,并将该节点的信息添加到调用它的范围。selectNode()方法选择整个节点,包括其后代节点,而 selectNodeContents()只选择节点的后代。
<!DOCTYPE html>
<html>
<body>
<p id="p1"><b>Hello</b> world!</p>
</body>
</html>
以下 JavaScript 代码可以访问并创建相应的范围:
let range1 = document.createRange(),
range2 = document.createRange(),
p1 = document.getElementById("p1");
range1.selectNode(p1);
range2.selectNodeContents(p1);
4.3 复杂选择
要创建复杂的范围,需要使用 setStart()和 setEnd()方法。这两个方法都接收两个参数:参照节点和偏移量。
4.4 操作范围
范围的起点和终点都在文本节点内部,并不是完好的 DOM 结构,所以无法在 DOM 中表示。不过,范围能够确定缺失的开始和结束标签,从而可以重构出有效的 DOM 结构,以便后续操作。
deleteContents()这个方法会从文档中删除范围包含的节点。
4.5 范围插入
向范围中插入内容。使用 insertNode()方法可以在范围选区的开始位置插入一个节点。
除了向范围中插入内容,还可以使用 surroundContents()方法插入包含范围的内容。这个方法接收一个参数,即包含范围内容的节点。
4.6 范围折叠
折叠范围可以使用 collapse()方法,这个方法接收一个参数:布尔值,表示折叠到范围哪一端。true 表示折叠到起点,false 表示折叠到终点。要确定范围是否已经被折叠,可以检测范围的collapsed属性:
range.collapse(true); // 折叠到起点
console.log(range.collapsed); // 输出 true
测试范围是否被折叠,能够帮助确定范围中的两个节点是否相邻。例如有以下 HTML 代码:
<p id="p1">Paragraph 1</p><p
id="p2">Paragraph 2</p>
let p1 = document.getElementById("p1"),
p2 = document.getElementById("p2"),
range = document.createRange();
range.setStartAfter(p1);
range.setStartBefore(p2);
console.log(range.collapsed); // true
在这种情况下,创建的范围是折叠的,因为 p1 后面和 p2 前面没有任何内容。
4.7 范围比较
如果有多个范围,则可以使用 compareBoundaryPoints()方法确定范围之间是否存在公共的边界(起点或终点)。这个方法接收两个参数:要比较的范围和一个常量值,表示比较的方式。这个常量参数包括:
Range.START_TO_START(0),比较两个范围的起点;
Range.START_TO_END(1),比较第一个范围的起点和第二个范围的终点;
Range.END_TO_END(2),比较两个范围的终点;
Range.END_TO_START(3),比较第一个范围的终点和第二个范围的起点。
compareBoundaryPoints()方法在第一个范围的边界点位于第二个范围的边界点之前时返回-1,在两个范围的边界点相等时返回 0,在第一个范围的边界点位于第二个范围的边界点之后时返回 1。
4.8 复制范围
调用范围的 cloneRange()方法可以复制范围。这个方法会创建调用它的范围的副本:
let newRange = range.cloneRange();
新范围包含与原始范围一样的属性,修改其边界点不会影响原始范围。
4.9 清理
在使用完范围之后,最好调用 detach()方法把范围从创建它的文档中剥离。调用 detach()之后,就可以放心解除对范围的引用,以便垃圾回收程序释放它所占用的内存。下面是一个例子:
range.detach(); // 从文档中剥离范围
range = null; // 解除引用
这两步是最合理的结束使用范围的方式。剥离之后的范围就不能再使用了。
5 小结
DOM2 规范定义了一些模块,用来丰富 DOM1 的功能。DOM2 Core 在一些类型上增加了与 XML命名空间有关的新方法。这些变化只有在使用 XML 或 XHTML 文档时才会用到,在 HTML 文档中则没有用处。DOM2 增加的与 XML 命名空间无关的方法涉及以编程方式创建 Document 和DocumentType类型的新实例。
DOM2 Style 模块定义了如何操作元素的样式信息。
每个元素都有一个关联的 style 对象,可用于确定和修改元素特定的样式。
要确定元素的计算样式,包括应用到元素身上的所有 CSS规则,可以使用getComputedStyle()方法。
通过 document.styleSheets 集合可以访问文档上所有的样式表。DOM2 Traversal and Range 模块定义了与 DOM 结构交互的不同方式。
NodeIterator 和 TreeWalker 可以对 DOM 树执行深度优先的遍历。
NodeIterator 接口很简单,每次只能向前和向后移动一步。TreeWalker 除了支持同样的行为,还支持在 DOM 结构的所有方向移动,包括父节点、同胞节点和子节点。
范围是选择 DOM 结构中特定部分并进行操作的一种方式。
通过范围的选区可以在保持文档结构完好的同时从文档中移除内容,也可复制文档中相应的部分。