第 16 章 DOM2 和 DOM3

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 结构中特定部分并进行操作的一种方式。
 通过范围的选区可以在保持文档结构完好的同时从文档中移除内容,也可复制文档中相应的部分。

  • 16
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值