第十章 DOM

152 阅读11分钟

节点层次

Node类型

DOM1 级定义了一个 Node 接口,该接口将由 DOM 中的所有节点类型实现。每个节点都有一个 nodeType 属性,用于表明节点的类型。节点类型由在 Node 类型中定义的下列12 个数值常量来表示:

  • Node.ELEMENT_NODE(1);

  • Node.ATTRIBUTE_NODE(2);

  • Node.TEXT_NODE(3);

  • Node.CDATA_SECTION_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)。

  1. nodeName 和 nodeValue 属性

    使用nodeName和nodeValue属性可以了解节点的具体信息。对于元素节点,nodeName 中保存的始终都是元素的标签名,而 nodeValue 的值则始终为 null

  2. 节点关系

    • 每个节点都有一个 childNodes 属性,其中保存着一个 NodeList 对象(可以通过方括号语法来访问 NodeList 的值,这个对象也有 length 属性—— 表示的是访问 NodeList 的那一刻,其中包含的节点数量)。NodeList对象是基于DOM结构动态执行查询的结果,可以通过方括号和item()访问节点
    var firstChild = someNode.childNodes[0]; 
    var secondChild = someNode.childNodes.item(1); 
    var count = someNode.childNodes.length; 
    
    • 每个节点都有一个 parentNode 属性,指向文档树中的父节点。
    • 每个节点都有的 previousSibling和 nextSibling 属性,分别指向上一个节点和下一个节点。列表中第一个节点的 previousSibling 属性值为 null,而列表中最后一个节点的 nextSibling 属性的值同样也为 null。
    • 父节点的 firstChild 和 lastChild属性分别指向其 childNodes 列表中的第一个和最后一个节点。
    • 所有节点都有的最后一个属性是 ownerDocument,该属性指向表示整个文档的文档节点。任何节点都属于它所在的文档,任何节点都不能同时存在于两个或更多个文档中。
  3. 操作节点

    操作的是某个节点的子节点,也就是说,要使用这几个方法必须先取得父节点(使用 parentNode 属性)。另外,并不是所有类型的节点都有子节点,如果在不支持子节点的节点上调用了这些方法,将会导致错误发生。

    • appendChild():向childNodes列表末尾添加一个节点。添加节点后,childNodes 的新增节点、父节点及以前的最后一个子节点的关系指针都会相应地得到更新,更新完成后,appendChild()返回新增的节点。如果传入到 appendChild()中的节点已经是文档的一部分了,那结果是将该节点从原来的位置转移到末尾。例如在调用 appendChild()时传入了父节点的第一个子节点,那么该节点就会成为父节点的最后一个子节点。
    • insertBefore():把节点放在 childNodes 列表中某个特定的位置上。两个参数:要插入的节点和作为参照的节点。插入节点后,被插入的节点会变成参照节点的前一个同胞节点,同时被方法返回。如果参照节点是null,则 insertBefore()与 appendChild()执行相同的操作
    • replaceChild():替换节点。两个参数:要插入的节点和要替换的节点。要替换的节点将由这个方法返回并从文档树中被移除,同时由要插入的节点占据其位置。
    • removeChild():移除节点。一个参数:要移除的节点
  4. 其他方法

    这些方法是所有节点共有的。

    • cloneNode():用于创建调用这个方法的节点的一个完全相同的副本,不会复制添加到 DOM 节点中的 JavaScript 属性。接收一个布尔值参数,表示是否进行深复制,值为true时执行深复制,也就是复制节点及其整个子节点树;值为false时执行浅复制,只复制节点本身。复制后返回的节点副本属于文档所有,但并没有为它指定父节点。
    • normalize():处理文档树中的文本节点。

Document类型

  1. 文档的子节点

    • document.documentElement:指向<html>元素
    • document.body:指向<body>元素
    • document.doctype:指向<!DOCTYPE>标签
  2. 文档信息

    • document.title:包含着<title>元素中的文本——显示在浏览器窗口的标题栏或标签页上。通过这个属性可以取得当前页面的标题,也可以修改当前页面的标题并反映在浏览器的标题栏中
    • document.URL:包含页面完整的 URL(即地址栏中显示的 URL),不可设置。
    • document.domain:包含页面的域名,可设置。将每个页面的document.domain 设置为相同的值,这些页面就可以互相访问对方包含的 JavaScript 对象了。域名一开始是“松散的”(loose),那么不能将它再设置为“紧绷的”(tight)。例如,在将 document.domain 设置为"wrox.com"之后,就不能再将其设置回"p2p.wrox.com",否则将会导致错误
    • document.referrer:保存链接到当前页面的那个页面的 URL,不可设置。在没有来源页面的情况下,referrer 属性中可能会包含空字符串
  3. 查找元素

    • getElementById():不存在匹配的元素时返回null,有多个匹配结果时返回第一个元素

    • getElementByTagName():返回一个HTMLCollection 对象。HTMLCollection 对象namedItem()方法,该方法可以通过元素的 name特性取得集合中的项。对命名的项也可以使用方括号语法来访问

      <img src="myimage.gif" name="myImage"> 
      var images = document.getElementsByTagName("img");
      //myImage1和myImage2表示同一个DOM对象
      var myImage1 = images.namedItem("myImage");
      var myImage2 = images["myImage"];  
      
    • getElementsByName():返回带有给定 name 特性的所有元素

  4. 特殊集合

    • document.anchors:包含文档中所有带 name 特性的<a>元素;
    • document.forms:包含文档中所有的<form>元素,与 document.getElementsByTagName("form")得到的结果相同;
    • document.images:包含文档中所有的<img>元素,与 document.getElementsByTagName ("img")得到的结果相同;
    • document.links:包含文档中所有带 href 特性的<a>元素。
  5. DOM 一致性检测

DOM1 级只为 document.implementation 规定了一个方法,即 hasFeature()。这个方法接受两个参数:要检测的 DOM 功能的名称及版本号。如果浏览器支持给定名称和版本的功能,则该方法返回 true

var hasXmlDom = document.implementation.hasFeature("XML", "1.0"); 
  1. 文档写入

    • document.write():写入到输出流,原样写入
    • document.writeln():写入到输出流,末尾添加一个换行符(\n)
    • document.open():打开网页的输出流
    • docum.close():关闭网页的输出流

Element类型

  1. 特性相关方法

    • getAttribute():取得特性。取得自定义的特性值使用该方法,取得公认的特性值的时候直接使用对象的属性获取
    • setAttribute():设置特性。接受两个参数:要设置的特性名和值。如果特性已经存在,setAttribute()会以指定的值替换现有的值;如果特性不存在,setAttribute()则创建该属性并设置相应的值。直接给属性赋值可以设置特性的值,但是为 DOM 元素添加一个自定义的属性,该属性不会自动成为元素的特性
    • removeAttribute():删除特性。不仅清除特性的值,而且会从元素中完全删除特性
  2. attributes属性

    • getNamedItem(name):返回 nodeName 属性等于 name 的节点;
    • removeNamedItem(name):从列表中移除 nodeName 属性等于 name 的节点;
    • setNamedItem(node):向列表中添加节点,以节点的 nodeName 属性为索引;
    • item(pos):返回位于数字 pos 位置处的节点。

    attributes 属性中包含一系列节点,每个节点的 nodeName 就是特性的名称,而节点的 nodeValue就是特性的值。要取得元素的 id 特性,可以使用以下代码 var id = element.attributes.getNamedItem("id").nodeValue; , 使用方括号语法访问节点 var id = element.attributes["id"].nodeValue; ,也可以使用这种语法来设置特性的值,即先取得特性节点,然后再将其 nodeValue 设置为新值,如下所示。element.attributes["id"].nodeValue = "someOtherId";

  3. 创建元素

    document.createElement():接受一个参数,要创建元素的标签。

    var div1 = document.createElement("div"); 
    div1.id = "myNewDiv"; 
    div1.className = "box"; 
    document.body.appendChild(div); 
    
    //在IE中可以以另一种方式使用 createElement()
    var div2 = document.createElement("\<div id=\"myNewDiv\" class=\"box\">\</div >"); 
    

Text类型

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

  1. 创建文本节点

    document.createTextNode():创建文本节点。接受一个参数:要插入节点中的文本。新建之后需要把文本节点添加到文档树中已经存在的节点中

    var element = document.createElement("div"); 
    element.className = "message"; 
    var textNode = document.createTextNode("Hello world!"); 
    element.appendChild(textNode); 
    document.body.appendChild(element); 
    
  2. 规范化文本节点

    normalize():将所有文本节点合并成一个节点,结果节点的 nodeValue 等于将合并前每个文本节点的 nodeValue 值拼接起来的值。

    var element = document.createElement("div"); 
    element.className = "message"; 
    var textNode = document.createTextNode("Hello world!"); 
    element.appendChild(textNode); 
    var anotherTextNode = document.createTextNode("Yippee!"); 
    element.appendChild(anotherTextNode); 
    document.body.appendChild(element); 
    alert(element.childNodes.length); //2 
    element.normalize(); 
    alert(element.childNodes.length); //1 
    alert(element.firstChild.nodeValue); // "Hello world!Yippee!" 
    
  3. 分割文本节点

    splitText():分割文本节点。接收一个参数:要开始分割的位置。将一个文本节点分成两个文本节点,即按照指定的位置分割 nodeValue 值。

    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); 
    alert(element.firstChild.nodeValue); //"Hello" 
    alert(newNode.nodeValue); //" world!" 
    alert(element.childNodes.length); //2 
    

Comment类型

注释在 DOM 中是通过 Comment 类型来表示的。可以通过 nodeValue 或 data 属性来取得注释的内容

document.createComment():创建注释节点,传递注释文本

CDATASection类型

CDATASection 类型只针对基于 XML 的文档,表示的是 CDATA 区域。

document.createCDataSection():创建 CDATA 区域

DocumentType类型

DocumentType包含着与文档的 doctype 有关的所有信息

  • document.doctype.name:文档类型的名称
  • document.doctype.entities:由文档类型描述的实体的 NamedNodeMap 对象
  • document.doctype.notations:由文档类型描述的符号的NamedNodeMap 对象

DocumentFragment类型

DocumentFragment是一种“轻量级”的文档,可以包含和控制节点,可以将它作为一个“仓库”来使用,即可以在里面保存将来可能会添加到文档中的节点

document.createDocumentFragment():创建文档片段

<ul id="myList"></ul> 

//使用一个文档片段来保存创建的列表项,然后再一次性将它们添加到文档中。
var fragment = document.createDocumentFragment(); 
var ul = document.getElementById("myList"); 
var li = null; 
for (var i=0; i \< 3; i++){
 li = document.createElement("li"); 
 li.appendChild(document.createTextNode("Item " + (i+1))); 
 fragment.appendChild(li); 
}
//文档片段的所有子节点都被删除并转移到了\<ul>元素中
ul.appendChild(fragment); 

Attr类型

元素的特性在 DOM 中以 Attr 类型来表示。

Attr 对象有 3 个属性:name、value 和 specified。其中,name 是特性名称(与 nodeName 的值相同),value 是特性的值(与 nodeValue 的值相同),而 specified 是一个布尔值,用以区别特性是在代码中指定的,还是默认的。

DOM操作技术

动态脚本

两种方式:插入外部文件和直接插入 JavaScript 代码

//插入外部文件
function loadScript(url){
    var script = document.createElement("script");
    script.type = "text/javascript";
    script.src = url;
    document.body.appendChild(script); 
}
//调用
loadScript("client.js");

//直接插入js代码
function loadScriptString(code){ 
    var script = document.createElement("script"); 
    script.type = "text/javascript"; 
    try {
        script.appendChild(document.createTextNode(code)); 
    } catch (ex){
        script.text = code;
    }
    document.body.appendChild(script); 
}
//调用
loadScrptStrng("uncton sayH(){alert('h');}")

动态样式

<link>元素用于包含来自外部的文件,<style>元素用于指定嵌入的样式。

//外部文件样式
function loadStyles(url){
    var link = document.createElement("link");
    link.rel = "stylesheet"; 
    link.type = "text/css"; 
    link.href = url; 
    var head = document.getElementsByTagName("head")[0]; 
    head.appendChild(link); 
}
loadStyles("styles.css");

//嵌入样式
function loadStyleString(css){
    var style = document.createElement("style");
    style.type = "text/css"; 
    try{
        style.appendChild(document.createTextNode(css)); 
    } catch (ex){
        style.styleSheet.cssText = css;
    }
    var head = document.getElementsByTagName("head")[0]; 
    head.appendChild(style); 
}
loadStyleString("body{background-color:red}"); 

操作表格

为了方便构建表格,HTML DOM 还为<table>、<tbody>和<tr>元素添加了一些属性和方法。

为<table>元素添加的属性和方法如下:

  • caption:保存着对<caption>元素(如果有)的指针。
  • tBodies:是一个<tbody>元素的 HTMLCollection。
  • tFoot:保存着对<tfoot>元素(如果有)的指针。
  • tHead:保存着对<thead>元素(如果有)的指针。
  • rows:是一个表格中所有行的 HTMLCollection。
  • createTHead():创建<thead>元素,将其放到表格中,返回引用。
  • createTFoot():创建<tfoot>元素,将其放到表格中,返回引用。
  • createCaption():创建<caption>元素,将其放到表格中,返回引用。
  • deleteTHead():删除<thead>元素。
  • deleteTFoot():删除<tfoot>元素。
  • deleteCaption():删除<caption>元素。
  • deleteRow(pos):删除指定位置的行。
  • insertRow(pos):向 rows 集合中的指定位置插入一行。

为<tbody>元素添加的属性和方法如下:

  • rows:保存着<tbody>元素中行的 HTMLCollection。
  • deleteRow(pos):删除指定位置的行。
  • insertRow(pos):向 rows 集合中的指定位置插入一行,返回对新插入行的引用。

为<tr>元素添加的属性和方法如下:

  • cells:保存着<tr>元素中单元格的 HTMLCollection。
  • deleteCell(pos):删除指定位置的单元格。
  • insertCell(pos):向 cells 集合中的指定位置插入一个单元格,返回对新插入单元格的引用。
//创建 table 
var table = document.createElement("table"); 
table.border = 1; 
table.width = "100%"; 
//创建 tbody 
var tbody = document.createElement("tbody"); 
table.appendChild(tbody); 
//创建第一行
tbody.insertRow(0); 
tbody.rows[0].insertCell(0); 
tbody.rows[0].cells[0].appendChild(document.createTextNode("Cell 1,1")); 
tbody.rows[0].insertCell(1); 
tbody.rows[0].cells[1].appendChild(document.createTextNode("Cell 2,1")); 
//创建第二行
tbody.insertRow(1); 
tbody.rows[1].insertCell(0); 
tbody.rows[1].cells[0].appendChild(document.createTextNode("Cell 1,2")); 
tbody.rows[1].insertCell(1); 
tbody.rows[1].cells[1].appendChild(document.createTextNode("Cell 2,2")); 
//将表格添加到文档主体中
document.body.appendChild(table); 

使用NodeList

NodeList 及其“近亲”NamedNodeMap 和 HTMLCollection,这三个集合都是“动态的”;换句话说,每当文档结构发生变化时,它们都会得到更新。浏览器不会将创建的所有集合都保存在一个列表中,而是在下一次访问集合时再更新集合。

//不能这样做,会导致死循环
var divs = document.getElementsByTagName("div"), 
    i, 
    div; 
for (i=0; i < divs.length; i++){ 
    div = document.createElement("div"); 
    document.body.appendChild(div); 
} 

var divs = document.getElementsByTagName("div"), 
    i,
    len, 
    div; 
for (i = 0, len = divs.length; i < len; i++){ 
    div = document.createElement("div"); 
    document.body.appendChild(div); 
}

这个例子中初始化了第二个变量 len。由于 len 中保存着对 divs.length 在循环开始时的一个快照,因此就会避免出现无限循环问题。