• DOM1(DOMLevel1)主要定义了HTML和XML文档的底层结构。
  • DOM2(DOMLevel2)和DOM3 ( DOM Level3 )在这些结构之上加入更多交互能力,提供了更高级的XML特性。
  • DOM2和DOM3是按照模块化的思路来制定标准的,每个模块之间有一定关联,但分别针对某个DOM子集。这些模式如下所示。
    • DOM Core:在DOM1核心部分的基础上,为节点增加方法和属性。
    • DOM Views:定义基于样式信息的不同视图。
    • DOM Events:定义通过事件实现DOM文档交互。
    • DOM Style:定义以编程方式访问和修改CSS样式的接口。
    • DOM Traversal and Range:新增遍历DOM文档及选择文档内容的接口。
    • DOM HTML:在DOM1 HTML部分的基础上,增加属性、方法和新接口。
    • DOM Mutation Observers:定义基于DOM变化触发回调的接口。这个模块是DOM4级模块,用于取代Mutation Events。

DOM的演进

  • DOM2和DOM3Core模块的目标是扩展DOMAPI,满足XML的所有需求并提供更好的错误处理和特性检测。
  • DOM2 Core没有新增任何类型,仅仅在DOM1 Core基础上增加了一些方法和属性。
  • DOM3 Core除了增强原有类型,也新增了一些新类型。

1、XML命名空间

1
2
3
4
5
6
7
8
<html xmlns="http://www.w3.org/1999/xhtml"> 
<head>
<title>Example XHTML page</title>
</head>
<body>
Hello world!
</body>
</html>
  • 对这个例子来说,所有元素都默认属于XHTML命名空间。可以使用xmlns给命名空间创建一个前缀,格式为“xmlns:前缀”,如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    <xhtml:html xmlns:xhtml="http://www.w3.org/1999/xhtml"> 
    <xhtml:head>
    <xhtml:title>Example XHTML page</xhtml:title>
    </xhtml:head>
    <xhtml:body>
    Hello world!
    </xhtml:body>
    </xhtml:html>
  • DOM2 Core为解决以下问题,给大部分DOM1方法提供了特定于命名空间的版本。

    • 创建了一个新元素,那这个元素属于哪个命名空间?
    • 查询特定标签名时,结果中应该包含哪个命名空间下的元素?

1)Node的变化

  • 在DOM2中,Node类型包含以下特定于命名空间的属性:
    • localName:不包含命名空间前缀的节点名;
    • namespaceURI:节点的命名空间URL,如果未指定则为null;
    • prefix:命名空间前缀,如果未指定则为null。
    • 在节点使用命名空间前缀的情况下,nodeName 等于prefix+ “:”+ localName。
  • DOM3进一步增加了如下与命名空间相关的方法:
    • isDefaultNamespace(namespaceURI),返回布尔值,表示namespaceURI是否为节点的默认命名空间。
    • lookupNamespaceURI(prefix),返回给定prefix的命名空间URI;
    • lookupPrefix(namespaceURI),返回给定namespaceURI的前缀。
1
2
3
4
5
6
7
8
9
10
11
<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>
1
2
3
4
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。
  • 这些命名控件特定的方法只在文档中包含两个或两个以上命名空间时才有用。

3)Element的变化

  • DOM2 Core对Element类型的更新主要集中在对属性的操作上。下面是新增的方法:
    • getElementsByTagNameNS (namespaceURI, tagName) ,取得指定命名空间namespaceURI中标签名为tagName的元素的NodeList;
    • hasAttributeNS (namespaceURI, localName), 返回布尔值,表示元素中是否有命名空间namespaceURI下名为localName的属性(注意,DOM2Core也添加不带命名空间的hasAttribute()方法);
    • removeAttributeNS (namespaceURI, localName) ,删除指定命名空间namespaceURI中名为localName的属性;
    • setAttributeNS (namespaceURI, qualifiedName,value),设置指定命名空间namespaceURI中名为qualifiedName的属性为value;
    • setAttributeNodeNS (attNode),为元素设置(添加)包含命名空间信息的属性节点attNode。
  • 这些方法与DOM1中对应的方法行为相同,除setAttributeNodeNS()之外都只是多了一个命名空间参数。

4)NamedNodeMap的变化

  • NamedNodeMap也增加了以下处理命名空间的方法。因为NamedNodeMap主要表示属性,所以这些方法大都适用于属性
    • getNamedItemNS (namespaceURI, localName), 取得指定命名空间namespaceURI中名为localName的项;
    • removeNamedItemNS (namespaceURI, localName),删除指定命名空间namespaceURI 中名为loca1Name的项;
    • setNamedItemNS (node),为元素设置(添加)包含命名空间信息的节点。

2、其他变化

1)DocumentType的变化

  • 新增了三个属性:publicId、systemId和internalSubset。publicId、systemId属性表示文档类型声明中有效但无法使用DOM1 API访问的数据。
1
<!DOCTYPE HTML PUBLIC "-// W3C// DTD HTML 4.01// EN" "http://www.w3.org/TR/html4/strict.dtd">
1
2
console.log(document.doctype.publicId); 
console.log(document.doctype.systemId);
  • internalsubset用于访问文档类型声明中可能包含的额外定义,如下面的例子所示:
1
2
3
<!DOCTYPE html PUBLIC "-// W3C// DTD XHTML 1.0 Strict// EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
[<!ELEMENT name (#PCDATA)>] >
  • 对于以上声明,document . doctype. internalSubset会返回”“。HTML文档中几乎不会涉及文档类型的内部子集,XML文档中稍微常用一些。

2)Document的变化

  • 唯一与命名空间无关的方法:importNode()。

    • 用于从其他文档获取一个节点并导入到新文档,以便将其插入新文档。
    • 接收两个参数:要复制的节点和表示是否同时复制子树的布尔值。
    • 返回结果是适合在当前文档中使用的新节点。
      1
      2
      let newNode = document.importNode(oldNode, true); // 导入节点及所有后代
      document.body.appendChild(newNode);
  • 由于每个节点都有一个ownerDocument属性,表示所属文档;如果调用appendChild()方法时传入节点的owerDocument不是指向当前文档,则会发生错误;而调用importNode()导入其他文档的节点会返回一个新节点,这个新节点的owerDocument属性时正确的。

  • DOM2 View给Document类型增加了新属性defaultView,是一个指向拥有当前文档的窗口的指针。

    • 并没有明确视图何时可用,因此这是添加的唯一一个属性。
    • 可用于确定拥有文档的窗口。
1
let parentWindow = document.defaultView || document.parentWindow;
  • DOM2 Core还针对document.implementation对象增加了两个新方法: createDocumentType ()和createDocument ()。

  • createDocumentType:

    • 用于创建DocumentType类型的新节点;
    • 接收三个参数:文档类型名称、publicId和systemld。
    • createDocumentType()只在创建新文档时才会用到。
  • createDocument():

    • 接收三个参数:文档元素的namespaceURI、文档元素的标签名和文档类型。
  • 要创建一个XHTML文档,可以使用以下代码:

    1
    2
    3
    let doctype = document.implementation.createDocumentType("html", "-// W3C// DTD XHTML 1.0 Strict// EN", "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"); 
    let doc = document.implementation.createDocument("http://www.w3.org/1999/xhtml",
    "html", doctype);
  • DOM2 HTML模块也为document.implamentation对象添加了createHTMLDocument()方法。

    • 方法可以创建一个完整的HTML文档,包含<html>、<head>、<title>和<body>元素。
    • 直接收一个参数:创建文档的标题(放到title元素中)
    • 返回一个新的HTML文档。
1
2
3
let htmldoc = document.implementation.createHTMLDocument("New Doc"); 
console.log(htmldoc.title); // "New Doc"
console.log(typeof htmldoc.body); // "object"

3)Node的变化

  • DOM3新增了两个用于比较节点的方法:isSameNode()和isEqualNode()。

    • 两个方法都接收一个参数:节点参数;
    • 返回值:如果这个节点与参考节点相同或相等,则返回true;反之返回false。
    • 节点相同,意味着引用同一个对象;
    • 节点相等,意味着节点类型相同,拥有相等的属性( nodeName、nodevalue等),而且attributes和childNodes也相等(即同样的位置包含相等的值)。
      1
      2
      3
      4
      5
      6
      7
      let div1 = document.createElement("div"); 
      div1.setAttribute("class", "box");
      let div2 = document.createElement("div");
      div2.setAttribute("class", "box");
      console.log(div1.isSameNode(div1)); // true
      console.log(div1.isEqualNode(div2)); // true
      console.log(div1.isSameNode(div2)); // false
  • setUserData():

    • 给DOM节点附加额外数据的方法。
    • 接收三个参数:键、值、处理函数。
      1
      document.body.setUserData("name", "Nicholas", function() {}); 
  • 处理函数会在包含数据的节点被复制、删除、重命名或导入其他文档的时候执行,可以在这时候决定如何处理用户数据。

  • 处理函数接收五个参数:表示操作类型的数值( 1代表复制,2代表导人,3代表删除,4代表重命名)、数据的键、数据的值、源节点和目标节点

  • 删除节点时,源节点为null;除复制外,目标节点都为null。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    let div = document.createElement("div"); 
    div.setUserData("name", "Nicholas", function(operation, key, value, src, dest) {
    if (operation == 1) {
    dest.setUserData(key, value, function() {});
    }
    });
    let newDiv = div.cloneNode(true);
    console.log(newDiv.getUserData("name")); // "Nicholas"
    /*
    这里先创建了一个<div>元素,然后给它添加了一-些数据,包含用户的名字。在使用cloneNode ()复制这个元素时,就会调用处理函数,从而将同样的数据再附加给复制得到的目标节点。然后,在副本节点上调用getUserData ()能够取得附加到源节点上的数据。
    */

4)内嵌窗格的变化

  • DOM2 HTML给HTMLIFrameElement类型新增了一个属性:contentDocument。
    • contentDocument属性时Document的实例,拥有所有文档属性和方法
  • contentWindow属性返回相应窗格的window对象,这个对象上有一个document属性。