DOM是針對HTML和XML文檔的一個API。DOM描繪了一個層次化的節(jié)點樹,允許開發(fā)人員添加、移除和修改頁面的某一部分。
IE中的所有DOM對象都是以COM對象的形式實現(xiàn)的。這意味著IE中的DOM對象與原生的JavaScript對象的行為或活動特點并不一致。
節(jié)點層次
<html>
<head>Sample Page</head>
<body>
<p>Hello World!</p>
</body>
</html>
文檔節(jié)點是每個文檔的根節(jié)點。這個例子中,文檔節(jié)點只有一個子節(jié)點,即html元素,我們稱之為文檔元素。文檔元素是文檔的最外層元素,文檔中的其他所有元素都包含在文檔元素中。每個文檔只能有一個文檔元素。在HTML頁面中,文檔元素始終都是<html>元素。在XML中,沒有預(yù)定義的元素,因此任何元素都可能成為文檔元素。
每一段標(biāo)記都可以通過樹中的一個節(jié)點來表示。
Node 類型
DOM1級定義了一個Node接口,該接口將由DOM中所有節(jié)點類型實現(xiàn)。這個Node接口在JavaScript中是作為Node類型實現(xiàn)的;除了IE之外,在其他所有瀏覽器中都可以訪問到這個類型。JavaScript中的所有節(jié)點類型都繼承自Node類型,因此所有節(jié)點類型都共享著相同的基本屬性和方法。
每個節(jié)點都有一個nodeType屬性,用于表面節(jié)點的類型。節(jié)點類型由在Node類型中定義的下列12個數(shù)值常量來表示,任何節(jié)點類型必居其一:
- Node.ELEMENT_NODE(1);
- Node.ATTRIBUTE_NODE(2);
- Node.TEXT_NODE(3);
- Node.CDATE_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);
if (someNode.nodeType == Node.ELEMENT_NODE) { //在IE中無效
alert("Node is an element.");
}
//由于IE沒有公開Node類型的構(gòu)造函數(shù),因此上面的的代碼在IE中會導(dǎo)致錯誤
if (someNode.nodeType == 1) { //適用于所有的瀏覽器
alert("Node is an element.");
}
并不是所有節(jié)點類型都收到Web瀏覽器的支持。
nodeName 和 nodeValue 屬性
對于元素節(jié)點,nodeName中保存的始終是元素的標(biāo)簽名,而nodeValue始終為null。
節(jié)點關(guān)系
每個節(jié)點都有一個childNodes屬性,其中保存著一個NodeList對象。NodeList是一種類數(shù)組對象,用于保存一組有序的節(jié)點,可以通過位置來訪問這些節(jié)點。NodeList對象的獨特之處在于,它實際上是基于DOM結(jié)構(gòu)動態(tài)執(zhí)行查詢的結(jié)果,因此DOM結(jié)構(gòu)的變化能夠自動反映在NodeList對象中。
var firstChild = someNode.childNodes[0];
var secondChild = someNode.childNodes.item(1);
var count = someNode.childNodes.length;
另外,要注意length屬性表示的是訪問NodeList的那一刻,其中包含的節(jié)點數(shù)量。可以用Array.prototype.slice()方法將NodeList對象轉(zhuǎn)化為數(shù)組。
//在IE8之前版本無效
var arrayOfNodes = Array.prototype.slice.call(someNode.childNodes, 0);
//由于IE8之前的版本將NodeList實現(xiàn)為一個COM對象。要想在IE中將NodeList轉(zhuǎn)化為數(shù)組,必須手動枚舉所有成員。
function convertToArray (nodes) {
var array = null;
try {
array = Array.prototype.slice.call(nodes, 0); //針對非IE瀏覽器
} catch (ex) {
array = new Array();
for (var i = 0, len = nodes.length; i < len; i++) {
array.push(nodes[i]);
}
}
return array;
}
每個節(jié)點都有一個parentNode屬性,該屬性指向文檔樹中的父節(jié)點。包含在childNodes列表中的所有節(jié)點都具有相同的父節(jié)點。此外,包含在childNodes列表中的每個節(jié)點相互之間都是同胞節(jié)點。通過使用列表中每個節(jié)點的previousSibling和nextSibling屬性,可以訪問同一列表中的其他節(jié)點。列表中的第一個節(jié)點的previousSibling屬性值為null,而列表中最后一個節(jié)點的nextSibling屬性的值同樣也為null。
if (someNode.nextSibling === null) {
alert("Last node in the parent`s childNodes list.");
} else if (someNode.previousSibling === null) {
alert("First node in the parent`s childNodes list.");
}
當(dāng)然,如果只有一個節(jié)點,那么該節(jié)點的previousSibling和nextSibling屬性都為null。
父節(jié)點的firstChild和lastChild屬性分別指向其childNodes列表中的第一個和最后一個節(jié)點。在只有一個子節(jié)點的情況下,firstChild和lastChild指向同一個節(jié)點。如果沒有子節(jié)點,那么firstChild和lastChild的值均為null。
hasChildNodes()這個方法在節(jié)點包含一個或多個子節(jié)點的情況下返回true。
所有節(jié)點都有的最后一個屬性是ownerDocument,該屬性指向表示整個文檔的文檔節(jié)點。
操作節(jié)點
因為關(guān)系指針是只讀的,所有DOM提供了一些操作節(jié)點的方法。
appendChild()用于向childNodes列表的末尾添加一個節(jié)點。更新完成后,appendChild()返回新增的節(jié)點。
var returnedNode = someNode.appendChild(newNode);
alert(returnedNode == newNode); //true
alert(returnedNode.lastChild == newNode); //true
如果傳入到appendChild()中的節(jié)點已經(jīng)是文檔的一部分了,那結(jié)果就是將該節(jié)點從原來的位置轉(zhuǎn)移到新位置。
因此,如果在調(diào)用appendChild()時傳入了父節(jié)點的第一個子節(jié)點,那么該節(jié)點就會成為父節(jié)點的最后一個子節(jié)點。
//someNode有多個子節(jié)點
var returnedNode = someNode.appendChild(someNode.firstChild);
alert(returnedNode == someNode.firstChild); //false
alert(returnedNode == someNode.lastChild); //true
insertBefore()方法可以把節(jié)點放在childNodes列表中某個特定的位置上。這個方法接收兩個參數(shù):要插入的節(jié)點和作為參考的節(jié)點。插入節(jié)點后,被插入的節(jié)點會變成參考節(jié)點的前一個同胞節(jié)點(previousSibling),同時被方法返回。如果參考節(jié)點是null,則insertBefore()與appendChild()執(zhí)行相同的操作。
//插入后成為最后一個子節(jié)點
returnedNode = someNode.insertBefore(newNode, null);
alert(newNode == someNode.lastChild); //true
//插入后成為第一個子節(jié)點
var returnedNode = someNode.insertBefore(newNode, someNode.firstChild);
alert(returnedNode == newNode); //true
alert(newNode == someNode.firstChild); //true
//插入到最后一個子節(jié)點前面
returnedNode = someNode.insertBefore(newNode, someNode.lastChild);
alert(newNode == someNode.childNodes[someNode, childNodes.length - 2]); //true
replaceChild()方法接收兩個參數(shù):要插入的節(jié)點和要替換的節(jié)點。要替換的節(jié)點將由這個方法返回并從文檔樹中被移除,同時由要插入的節(jié)點占據(jù)其位置。
//替換第一個子節(jié)點
var returnedNode = someNode.replaceChild(newNode, someNode.firstChild);
//替換最后一個子節(jié)點
returnedNode = someNode.replaceChild(newNode, someNode.lastChild);
在使用replaceChild()插入一個節(jié)點時,該節(jié)點的所有關(guān)系指針都會從被它替換的節(jié)點復(fù)制過來。盡管從技術(shù)上講,被替換的節(jié)點仍然還在文檔中,但它在文檔中已經(jīng)沒有了自己的位置。
removeChild()方法接收一個參數(shù),既要移除的節(jié)點。被移除的節(jié)點將成為方法的返回值。
//移除第一個子節(jié)點
var formerFirstChild = someNode.removeChild(someNode.firstChild);
//移除最后一個子節(jié)點
var formerLastChild = someNode.removeChild(someNode.lastChild);
通過removeChild()移除的節(jié)點仍然為文檔所有,只不過在文檔中已經(jīng)沒有了自己的位置。
如果在不支持子節(jié)點的節(jié)點上調(diào)用了這些方法,將會導(dǎo)致錯誤發(fā)生。
其他方法
有兩個方法是所有類型的節(jié)點都有的:
-
cloneNode(),用于創(chuàng)建調(diào)用這個方法的節(jié)點的一個完全相同的副本。接收一個布爾值參數(shù),表示是否執(zhí)行深復(fù)制。在參數(shù)為true時,執(zhí)行深復(fù)制,也就是復(fù)制節(jié)點及其整個子節(jié)點樹;在參數(shù)為false,執(zhí)行淺復(fù)制,即只復(fù)制節(jié)點本身。復(fù)制后返回的節(jié)點副本屬于文檔所有,但并沒有為它制定父節(jié)點。 -
normalize(),這個方法唯一的作用就是處理文檔樹中的文本節(jié)點。由于解析器的實現(xiàn)或DOM操作等原因,可能會出現(xiàn)文本節(jié)點不包含文本,或者連接出現(xiàn)兩個文本節(jié)點的情況。當(dāng)某個節(jié)點上調(diào)用這個方法時,就會在該節(jié)點的后代節(jié)點中查找上述兩種情況。如果找到了空文本節(jié)點,則刪除它;如果找到相鄰的文本節(jié)點,則將它們合并為一個文本節(jié)點。
cloneNode()方法不會復(fù)制添加到DOM節(jié)點中的JavaScript屬性,例如事件處理程序等。這個方法只復(fù)制特性、(在明確指定的情況下也復(fù)制)子節(jié)點,其他一切都不會復(fù)制。IE在此存在一個BUG,即它會復(fù)制事件處理程序,所以我們建議在復(fù)制之前最好先移除事件處理程序。
Document 類型
在瀏覽器中,document對象是HTMLDocument(繼承自Document類型)的一個實例,表示整個HTML頁面。而且,document對象是window對象的一個屬性,因此可以將其作為全局對象來訪問。
Document節(jié)點具有下列特性:
- nodeType的值為9
- nodeName的值為“#document”
- nodeValue的值為null
- parentNode的值為null
- ownerDocument的值為null
- 其子節(jié)點可能是一個DocumentType(最多一個)、Element(最多一個)、ProcessingInstruction或Comment。
Document類型可以表示HTML頁面或者其他基于XML的文檔。不過,最常見的應(yīng)用還是作為HTMLDocument實例的document對象。
在Safari、Firefox、Chrome和Opera中,可以通過腳本訪問Document類型的構(gòu)造函數(shù)和原型。但在所有瀏覽器中都可以訪問HTMLDocument類型的構(gòu)造函數(shù)和原型,包括IE8及后續(xù)版本。
文檔的子節(jié)點
document對象的documentElement屬性始終指向HTML頁面中的<html>元素。
<html>
<body>
</body>
</html>
var html = document.documentElement; //取得對<html>的引用
alert(html === document.childNodes[0]); //true
alert(html === document.firstChild); //true
document對象還有一個body屬性,直接指向<body>元素。
var body = document.body; //取得對<body>的引用
所有瀏覽器都支持document.documentElement和document.body屬性。
Document另一個可能的子節(jié)點是DocumentType。通常將<!DOCTYPE>標(biāo)簽看出一個與文檔其他部分不同的實體,可以通過doctype屬性(在瀏覽器中是document.doctype)來訪問它的信息。
var doctype = document.doctype; //取得對<!DOCTYPE>的引用
瀏覽器對document.doctype的支持差別很大:
- IE8及之前的版本:如果存在文檔類型聲明,會將其錯誤地解釋為一個注釋并把它當(dāng)作Comment節(jié)點;而
document.doctype的值始終為null。 - IE9+及Firefox:如果存在文檔類型什么,則將其作為文檔的第一個子節(jié)點;
document.doctype是一個DocumentType節(jié)點,也可以通過document.firstChild或document.childNodes[0]訪問同一個節(jié)點。 - Safari、Chrome和Opera:如果存在文檔類型聲明,則將其解析,但不作為文檔的子節(jié)點。
document.doctype是一個DocumentType節(jié)點,但該節(jié)點不會出現(xiàn)在document.childNodes中。
從技術(shù)上說,出現(xiàn)在<html>元素外部的注釋應(yīng)該算是文檔的子節(jié)點。然而,不同的瀏覽器在是否解析這些注釋以及能否正確處理它們等方面,也存在很大差異。
<!-- 第一條注釋 -->
<html>
<body>
</body>
</html>
<!-- 第二條注釋 -->
從邏輯上講,我們會認(rèn)為document.childNodes中應(yīng)該包含3項,但是實際上存在差異。
- IE8及之前版本、Safari3.1及更高版本、Opera和Chrome只為第一條注釋創(chuàng)建節(jié)點,不為第二條注釋創(chuàng)建節(jié)點。結(jié)果,第一條注釋就會成為
document.childNodes中的第一個子節(jié)點。 - IE9及更高版本會將第一條注釋創(chuàng)建為
document.childNodes中的一個注釋節(jié)點,也會將第二條注釋創(chuàng)建為document.childNodes中的注釋子節(jié)點。 - Firefox以及Safari3.1之前的版本完全忽略這兩條注釋。
文檔信息
document對象還有一些標(biāo)準(zhǔn)的Document對象所沒有的屬性。
title屬性包含著<title>元素中的文本。
//取得文檔標(biāo)題
var originalTitle = document.title;
//設(shè)置文檔標(biāo)題
document.title = "New page title";
URL屬性中包含頁面完整的URL(即地址欄中顯示的URL)。
domain屬性中只包含頁面的域名。
referrer屬性中保存著鏈接到當(dāng)前頁面的那個頁面的URL。在沒有來源的情況下,referrer屬性中可能會包含空字符串。
//取得完整的URL
var url = document.URL;
//取得域名
var domain = document.domain;
//取得來源頁面的URL
var referrer = document.referrer;
在這3個屬性中,只有domain是可以設(shè)置的,但也有安全方面的限制。
//假設(shè)頁面來自p2p.wrox.com域
document.domain = "wrox.com"; //成功
document.domain = "nczo.net"; //出錯
當(dāng)頁面中包含來自其他子域的框架或內(nèi)嵌框架時,能夠設(shè)置document.domain就非常方便了。由于跨域安全限制,來自不同子域的頁面無法通過JavaScript通信。而通過將每個頁面的document.domain設(shè)置為相同的值,這些頁面就可以互相訪問對方包含的JavaScript對象了。
瀏覽器對domain屬性還有一個限制,即域名一開始是松散的,那么不能將它再設(shè)置為緊繃的。
//假設(shè)頁面來自于p2p.wrox.com域
document.domain = "wrox.com"; //松散的,成功
document.domain = "p2p.wrox.com"; //緊繃的,出錯
所有瀏覽器中都存在這一限制,但IE8是實現(xiàn)這一限制的最早IE版本。
查找元素
getElementById(),接受一個參數(shù):要取得的元素的ID。如果找到則返回該元素,沒找到則返回null。這里的ID必須與頁面中元素的id特性(attribute)匹配,包括大小寫。
<div id="myDiv">Some text</div>
var div = document.getElementById("myDiv"); //取得<div>元素的引用
var div = document.getElementById("mydiv"); //無效的ID (在IE7及更早的版本可以)
IE8及較低版本不區(qū)分ID的大小寫。
如果頁面中多個元素的ID相同,getElementById()只返回文檔中第一個出現(xiàn)的元素。
IE7及較低版本還為此方法添加了一個怪癖:name特性與給定ID匹配的表單元素(<input><textarea><button><select>)也會被方法返回。
<input type="text" name="myElement" value="Text field">
<div id="myElement">A div</div>
<!-- document.getElementById("myElement")會返回<input>元素 -->
getElementByTagName(),接受一個參數(shù),即要取得的元素的標(biāo)簽名,返回的是包含零或多個元素的NodeList。在HTML文檔中,這個方法會返回一個HTMLCollection對象,作為一個“動態(tài)”集合,該對象與NodeList非常相似。
var images = document.getElementById("img");
alert(images.length); //輸出圖像的數(shù)量
alert(images[0].src); //輸出第一個圖像元素的src特性
alert(images.item(0).src); //輸出第一個圖像元素的src特性
HTMLCollection對象有一個方法namedItem(),通過元素的name特性取得集合中的項。
<img src="myimage.gif" name="myImage">
var myImage = images.namedItem("myImage");
HTMLCollection還支持按名稱訪問項:
var myImages = images["myImage"];
對于HTMLCollection,在后臺對數(shù)值索引就會調(diào)用item(),而對于字符串索引就會調(diào)用namedItem()。
//取得文檔中的所有元素
var allElements = document.getElementsByTagName("*");
//由于在IE將注釋(comment)實現(xiàn)為元素(element),因此在IE中調(diào)用getElementByTagName("*")將會返回所有的注釋節(jié)點。
getElementsByName()方法返回帶有給定name特殊的所有元素。返回的也是一個HTMLCollection。
特殊集合
除了屬性和方法,document對象還有一些特殊的集合。這些集合都是HTMLCollection對象。
- document.anchors 包含文檔中所有帶name特性的<a>元素。
- document.applets 包含文檔中所有的<applet>元素。
- document.forms 包含文檔中所有的<form>元素,等于document.getElementsByTagName("form")
- document.images 包含了文檔中所有的<img>元素
- document.links 包含了文檔中所有的帶href特性的<a>元素
上面集合始終可以通過HTMLDocument對象訪問到,而且和HTMLCollection對象類似,集合中的項也會隨著當(dāng)前文檔內(nèi)容的更新而更新。
DOM 一致性檢測
document.implementation屬性提供了瀏覽器實現(xiàn)DOM的哪些部分的相應(yīng)信息和功能的對象。
DOM1級只為document.implementation規(guī)定了一個方法hasFeature()。該方法接受兩個參數(shù):要檢測的DOM功能的名稱及版本號。支持的話就返回true。
var hasXmlDom = document.implementation.hasFeature("XML","1.0");

有時候返回true也不意味著一定和規(guī)范一致。
文檔寫入
write()和writeln()方法都接受一個字符串參數(shù),即要寫入到輸出流中的文本。write()會原樣寫入,而writeln()會在字符串的末尾添加一個換行符(\n)。
<html>
<head>
<title>document.write() Example</title>
</head>
<body>
<p>The current date and time is :
<script>
document.write("<strong>" + (new Date()).toString() + "</strong>");
</script>
</p>
</body>
</html>
open()和close()分別用于打開和關(guān)閉網(wǎng)頁的輸入流。
Element類型
Element類型用于表現(xiàn)XML或HTML元素,提供了對元素標(biāo)簽名、子節(jié)點及特性的訪問。Element節(jié)點具有以下特征:
- nodeType的值為1;
- nodeName的值為元素的標(biāo)簽名;
- nodeValue的值為null
- parentNode可能是Document或Element;
- 其子節(jié)點可能是Element、Text、Comment、ProcessingInstruction、CDATASection或EntityReference
要訪問元素的標(biāo)簽名,可以使用nodeName或tagName(主要為了清晰起見)屬性。
<div id="myDiv"></div>
var div = document.getElementById("myDiv");
alert(div.tagName); //"DIV"
alert(div.tagName == div.nodeName); //true
在HTML中,標(biāo)簽名始終是全部大寫;而在XML中,標(biāo)簽名則始終會與源代碼一樣。
HTML元素