《JavaScript高级程序设计》Chapter 10 DOM(文档对象模型)

节点层次

  1. DOM 可以将任何 HTML 和 XML 文档描绘成一个由多层节点构成的结构。节点分为几种不同的类型,每种类型分别表示文档中不同的信息和标记。每个节点都拥有各自的特点、数据和方法,另外也与其他节点存在某种关系。节点之间的关系构成了层次,而所有页面标记则表现为一个以特定节点为根节点的树形结构。

  2. Node 类型

    • DOM1 级定义了一个 Node 接口,该接口将由 DOM 中的所有节点类型实现。这个 Node 接口在 JavaScript 中是作为 Node 类型实现的;除了 IE 之外,在其他所有浏览器中都可以访问到这个类型。JavaScript 中的所有节点类型都继承自 Node 类型,因此所有节点类型由在 Node 类型中定义的下列 12 个数值常量表示:
      • Node.ELEMENT_NODE(1);
      • Node.ATTRIBUTE_NODE(2);
      • Node.TEXT_NODE(3);
      • Node.CDATA_SELECTION_NODE(4);
      • Node.ENTITY_REFERENCE_NODE(5);
      • Node.ENTITY_NODE(6);
      • Node.PROCESSING_INSTRUCTION_NODE(7);
      • Node.COMMENT_NODE(8);
      • Node.DOCUMENT_NODE(9);
      • Node.DOCUMENT_TYPE_NODE(10);
      • Node.DOCUMENT_FRAGMENT_NODE(11);
      • Node.NOTATION_NODE(12);
    • 将 nodeType 属性与数字值进行比较,可以确定节点的类型。
    if(someNode.nodeType == 1) {
        alert('Is a Element');
    }
    
    • 详细信息:
      • nodeName 和 nodeValue 属性

        • 对于元素节点,nodeName 中保存的始终都是元素的标签名,而 nodeValue 的值则始终为 null。
      • 节点关系

        • 每一个节点都有一个 childNodes 属性,其中保存着一个 NodeList 对象。NodeList 是一种类数组对象,用于保存一组有序的节点,可以通过位置来访问这些节点。但是,NodeList 不是 Array 的实例,它实际上是基于 DOM 结构动态执行查询的结果,因此 DOM 结构的变化能够自动反映在 NodeList 对象中。由于 IE8 及更早版本将 NodeList 实现为一个 COM 对象,所以不能使用正常的操作方法操作这种对象。必须手动枚举 NodeList 中的所有成员
        function convertToArray(nodes) {
            var array = null;
            try {
                array = Array.prototype.slice.call(nodes, 0);
            } catch (ex) {
                array = new Array();
                for(var i = 0, len = nodes.length; i < len; i++) {
                    array.push(nodes[i]);
                }
            }
            return array;
        }
        
        • 每一个节点都有一个 parentNode 属性,该属性指向文档树种的父节点。包含在 childNodes 列表中的所有节点都具有相同的父节点,因此它们的 parentNode 属性都指向同一个节点。包含在 childNodes 列表中的每个节点相互之间都是同胞节点。通过使用列表中每个节点的 previousSibling 属性和 nextSibling 属性,可以访问同一列表中的其他节点。
        • 父节点的 firstChild 和 lastChild 属性分别指向其 childNodes 列表中的第一个和最后一个节点。
        • hasChildNodes() 在节点包含一个或多个子节点的情况下返回 true。(比查询 length 更简单)
        • ownerDocument 属性指向表示整个文档的文档节点。任何节点都属于它所在的文档,任何节点都不能同时存在于两个或多个文档中。用此属性可以直接访问文档节点。
      • 操作节点

        • appendChild() 用于向 childNodes 列表的末尾添加一个节点。添加节点后,childNodes 的新增节点、父节点及以前的最后一个子节点的关系指针都会相应地得到更新。更新完成后,appendChild() 返回新增的节点
        • 任何 DOM 节点都不能同时出现在文档中的多个位置上。因此,如果在调用 appendChild() 时传入了父节点的第一个子节点,那么该节点就会成为父节点的最后一个子节点。
        • insertBefore() 将节点放在 childNodes 列表中某个特定的位置上。接收两个参数:要插入的节点和作为参照的节点。要插入的节点会作为变成参照节点的前一个同胞节点(previousSibling),同时被方法返回。如果参照节点是 null ,则与 appendChild() 相同。
        • replaceChild() 替换节点。接收两个参数:要插入的节点和要被替换的节点。将由这个方法删除要被替换的节点,同时由要插入的节点占据其位置。
        • removeChild() 移除节点。返回被移除的节点。
      • 其他方法

        • cloneNode() 用于创建调用这个方法的节点的一个完全相同的副本。接收一个 boolean 类型参数,表示是否执行深复制(复制节点及其整个子节点树)。如果是 false 执行浅复制(只复制节点本身)。
        • normalize() 处理文档树中的文本节点。当在某个节点上调用这个方法时,就会在该节点的后代节点中查找上述两种情况。如果找到了空文本节点则删除它;如果找到了相邻的文本节点,则将它们合并为一个文本节点。
  3. Document 类型

    • 在浏览器中,document 对象是 HTMLDocument(继承自 Document 类型)的一个实例,表示整个 HTML 页面。
    • Document 节点具有如下特征:
      • nodeType 9
      • nodeName '#document'
      • nodeValue null
      • parentNode null
      • ownerDocument null
      • 子节点可能是一个 DocumentType(最多一个)、Element(最多一个)、ProcessingInstruction 或 Comment 。
    • Document 类型可以表示 HTML 页面或者其他基于 XML 的文档。最常见的应用还是作为 HTMLDocument 实例的 document 对象。
    • Document 节点有两个内置的访问其子节点的快捷方式:documentElement 属性,始终指向 HTML 页面中的 <html> 元素;通过 childNodes 列表访问文档元素。
    • 作为 HTMLDocument 的实例,document 对象还有一个 body 属性,直接指向 <body> 元素。
    • Document 另一个可能的子节点是 DocumentType。通常将 <!DOCTYPE> 标签看成一个与文档其他部分不同的实体,可以通过 doctype 属性(在浏览器中是 document.doctype )来访问它的信息。但是在 IE8 中会将 DOCTYPE 作为注释,所以 doctype 值始终为 null。
    • document 的其他标准属性
      • title 包含 <title> 元素中的文本。
      • URL 包含页面完整的 URL。
      • domain 包含页面的域名。
      • referrer 保存着链接到当前页面的那个页面的 URL;在没有来源页面的情况下,referrer 属性可能包含空字符串。
    • 查找元素
      • getElementById()
      • getElementsByTagName()
      • IE8 以下,元素 ID 不区分大小写。
      • namedItem() 通过元素的 name 特性取得集合中的项。(也可以使用 [] 进行访问)
      • 调用 document.getElementsByTagName('*') 取得文档中的所有元素。
      • (只有 HTMLDocument 才有)getElementsByName() (如果使用 namedItem() 只会取得所有与传入 name 吻合的元素中的第一个元素)
    • 特殊集合
      • document.anchors
      • document.applets
      • document.forms
      • document.images
      • document.links
    • DOM 一致性检测
      • document.implementation 检测浏览器实现了 DOM 的哪些部分。
        • DOM1 只为 document.implementation 规定了 hasFeature(),接受两个参数:要检测的 DOM 功能的名称及版本号。如果浏览器支持给定名称和版本的功能,则返回 true。(查文档)
    • 文档写入
      • write() / writeln() 接收一个字符串参数,即要写入到输出流中的文本。
        • 在文档加载结束后调用 document.write() 那么输出的内容将会重写整个页面。
      • open() / close() 打开 / 关闭 当前网页的输出流。如果在页面加载期间使用 write() / writeln(),则不需要使用这两个方法。
  4. Element 类型

    • Element 类型用于表现 XML 或 HTML 元素,提供了对元素标签名、子节点及特性的访问。Element 节点具有以下特性:

      • nodeType 1
      • nodeName 元素标签名
      • nodeValue null
      • parentNode 可能是 Document 可能是 null
      • 其他子节点可能是 Element Text Comment ProcessingInstruction CDATASection EntityReference
    • 访问元素的标签名,可以使用 nodeName 也可以使用 tagName。(在 HTML 中,标签名始终以全部大写表示,而 XML 则始终与源代码中保持一致。)

    • HTML 元素的关联关系(查手册)

    • 取得特性(不区分大小写)

      • getAttribute()
    • 设置特性

      • setAttribute()
      • removeAttribute()
    • attributes 属性

      • Element 类型是使用 attributes 属性的唯一一个 DOM 类型节点。attributes 属性中包含一个 NamedNodeMap,与 NodeList 类似,也是一个”动态“的集合。元素的每一个特性都由一个 Attr 节点表示,每个节点都保存在 NamedNodeMap 对象中。NamedNodeMap 对象拥有下列方法:
      • getNamedItem(name) 返回 nodeName 属性等于 name 的节点;
      • removeNamedItem(name) 从列表中移除 nodeName 属性等于 name 的节点;
      • setNamedItem(node) 向列表中添加节点,以节点的 nodeName 属性为索引;
      • item(pos) 返回位于数字 pos 位置处的节点。
      • attribute 属性中包含一系列节点,每个节点的 nodeName 就是特性的名称,而节点的 nodeValue 就是特性的值。要取得元素的 id 特性,可以使用如下代码:
      var id = element.attributes.getNamedItem('id').nodeValue;
      var id_1 = element.attributes['id'].nodeValue;
      
      • 在 IE7- 中,所有被指定的特性或者通过 setAttribute() 设置的特性,其 specified 属性值都会变成 true。
    • 创建元素

      • document.createElement() 接收一个参数:要创建元素的标签名或完整标签。
      var div = document.createElement('div');
      var newDiv = document.createElement('<div id=\"myNewDiv\" class=\"box\"></div>');
      
    • 元素的子节点

      • 元素可以有任意数目的子节点和后代节点。
      • 元素支持 getElementsByTagName() 方法。在通过元素调用这个方法时,除了搜索起点是当前元素之外,其他方面都跟通过 document 调用这个方法相同,因此结果只会返回当前元素的后代。
  5. Text 类型

    • 文本节点由 Text 类型表示,包含的是可以照字面解释的纯文本内容。纯文本中可以包含转义后的 HTML 字符,但不能包含 HTML 代码。Text 节点具有以下特征:

      • nodeType 3
      • nodeName '#text'
      • nodeValue 节点所包含的文本
      • parentNode Element
      • 不支持(没有)子节点
      • 可以通过 nodeValue 或 data 属性访问 Text 节点中包含的文本,这两个属性中包含的值相同。对 nodeValue 的修改也会通过 data 反映出来,反之亦然。
      • appendData(text) 将 text 添加到节点末尾
      • deleteData(offset, count) 从 offset 指定的位置开始删除 count 个字符。
      • insertData(offset, text) 在 offset 指定的位置插入 text
      • replaceData(offset, count, text) 用 text 替换从 offset 指定的位置开始到 offset + count 为止处的文本。
      • splitText(offset) 从 offset 指定的位置将当前文本节点分成两个文本节点。
      • subStringData(offset, count) 提取从 offset 指定的位置开始到 offset + count 位置处的字符串。
      • length 保存节点中字符的数目。(nodeValue.length == data.length)
      • 每个可以包含内容的元素最多只能有一个文本节点,而且必须确实有内容存在。
    • 创建文本节点

      • document.createTextNode() 接收一个参数:要插入节点中的文本。
      • 在创建文本节点时,也会为其设置 ownerDocument 属性。不过,除非把新节点添加到文档树中已经存在的节点中,否则不会在浏览器窗口中看到新的节点。
      var element = document.createElement('div');
      element.className = 'message';
      var textNode = document.createTextNode('<strong>Hello</strong> world!');
      element.appendChild(textNode);
      
      document.body.appendChild(element);
      
    • 规范化文本节点

      • 合并相邻文本节点,由 Node 类型定义,名叫 normalize()。如果在一个包含两个或者多个文本节点的父元素上调用 normalize() 方法,则会将所有文本节点合并成一个节点,结果节点的 nodeValue 值等于将合并前每个文本节点的 nodeValue 值拼接起来的值。
      var element = document.createElement('div');
      element.className = 'message';
      var textNode = document.createTextNode('Hello');
      var anotherTextNode = document.createTextNode('World');
      element.appendChild(textNode);
      element.appendChild(anotherTextNode);
      
      element.normalize();
      
    • 分割文本节点

      • splitText() 将一个文本节点分成两个文本节点,按照指定的位置分割 nodeValue 值。原文本节点将包含从头开始到指定位置之前的内容,新的文本节点包含剩下的文本。该方法返回一个新的文本节点,该节点与原节点的 parentNode 相同。
      var element = document.createElement('div');
      element.className = 'message';
      var textNode = document.createTextNode('Hello World');
      element.appendChild(textNode);
      document.body.appendChild(element);
      var newNode = element.firstChild.splitText(5);
      
  6. Comment 类型

    • 注释在 DOM 中是通过 Comment 类型来表示的。Comment 节点具有下列特征:
      • nodeType 8
      • nodeName '#comment'
      • nodeValue 注释内容
      • parentNode Document / Element
      • 不支持子节点
    • 与 Text 类型继承自相同的基类,拥有除 splitText 外所有字符串的操作方法。与 Text 类型相似,也可以通过 nodeValue 或 data 属性取得注释内容。
    • 使用 document.createComment() 并为其传递注释文本可以创建注释节点。
  7. CDATASection 类型

    • CDATASection 类型值针对基于 XML 的文档,表示 CDATA 区域。CDATASection 节点具有下列特征:
      • nodeType 4
      • nodeName "#cdata-section"
      • nodeValue CDATA 区域中的内容
      • parentNode Document / Element
    • 与 Text 类型吧继承自相同的基类,拥有除 splitText 外所有字符串的操作方法。
    • CDATA 区域只会出现在 XML 文档中,因此多数浏览器会把 CDATA 区域错误的解析为 Comment 或 Element。在真正的 XML 文档中,可以使用 document.createCDataSection() 来创建 CDATA 区域。
  8. DocumentType 类型

    • 仅有 Firefox、Safari、Opera 支持。包含于文档的 doctype 有关的所有信息。具有如下特征:
      • nodeType 10
      • nodeName doctype 的名称
      • nodeValue null
      • parentNode Document
    • 在 DOM1 中,DocumentType 对象智能通过解析文档代码的方式来创建。支持它的浏览器会把 DocumentType 对象保存在 document.doctype 中。DOM1 描述了 DocumentType 的三个属性:name entities notations。
  9. DocumentFragment 类型

    • DocumentFragment 在文档中没有对应的标记。DOM 规定文档片段 (document fragment) 是一种”轻量级“的文档,可以包含和控制节点,但不会像完整的文档那样占用额外的资源。DocumentFragment 节点具有下列特征:
      • nodeType 11
      • nodeName "#document-fragment"
      • nodeValue null
      • parentNode null
      • 子节点可以是 Element ProcessingInstruction Comment Text CDATASection 或 EntityReference
    • 创建 DocumentFragment 对象可以使用 document.createDocumentFragment() 方法。
    • DocumentFragment 继承了 Node 的所有方法。可以通过 appendChild() 或 insertChild() 将文档片段中的内容添加到文档中。将文档片段作为参数传递给这两个方法时,实际上只会将文档片段的所有子节点添加到相应位置上;文档片段本身永远不会称为文档树的一部分。
  10. Attr 类型

    • 元素的特性在 DOM 中以 Attr 类型来表示。在所有浏览器中都可以访问 Attr 类型的构造函数和原型。从技术角度来讲,特性就是存在于元素的 attributes 属性中的节点。特性节点具有下列特征:
      • nodeType 2
      • nodeName 特性名称
      • nodeValue 特性的值
      • parentNode null
    • Attr 对象有三个属性,name value specified。其中,name 是特性名称,value 是特性的值,specified 是布尔值,用以区别特性是代码中指定的还是默认的。
    • 使用 document.createAttribute() 并传入特性的名称可以创建新的特性节点。
    var attr = document.createAttribute('align');
    attr.value = 'left';
    element.setAttributeNode(attr);
    

DOM 操作技术

  1. 动态脚本

    • 动态脚本指的是在页面加载时不存在,但是将来的某个时刻通过修改 DOM 动态添加的脚本。

    • 创建动态脚本可以插入外部文件,也可以直接插入 JavaScript 代码。

      <script type="text/javascript" src="client.js"></script>
      

      动态创建这个节点的 DOM

      var script = document.createElement("script");
      script.type = "text/javascript";
      script.src = "client.js";
      document.body.appendChild(script);
      
    • 行内 JavaScript 代码也可以使用类似的方式添加。

    • 使用这种方式要考虑兼容性问题。

    • 以这种方式加载的代码会在全局作用域中执行,而且当脚本执行后立即可用。实际上,这样执行的代码与在全局作用域中把相同的字符串传递给 eval() 是一样的。

  2. 动态样式

    • 方法类似于动态脚本。
    • 必须将 <link> 元素添加到 <head> 而不是 <body> 中,才能保证在所有浏览器中的行为一致。
    • 加载样式与执行 JavaScript 代码是异步的,后边的章节给出了利用时间检测这个过程是否完成的方法。
    • IE 将 <style> 视为一个特殊的、与 <script> 类似的节点,不允许访问其子节点。通过访问元素的 styleSheet 属性中的 cssText 属性可以使其接受 CSS 代码。
    var style = document.createElement("style");
    style.type = "text/css";
    try {
        style.appendChild(document.createTextNode("body {background-color: red}"));
    } catch (ex) {
        style.styleSheet.cssText = "body {background-color: red}";
    var head = document.getElementByTagName("head")[0];
    head.appendChild(style);
    
  3. 表格操作

    • DOM 为 <table> <tbody> <tr>元素添加了一些属性和方法,以方便表格的创建。(查手册)
  4. 使用 NodeList

    • 每当文档结构发生变化时,NodeList NamedNodeMap 和 HTMLCollection 都会得到更新。从本质上说,所有的 NodeList 对象都是在访问 DOM 文档时实时运行的查询。
    • 使用迭代方法对 NodeList 进行操作时,最好使用第二个变量保存 NodeList 的 length。

推荐阅读更多精彩内容