DOM(文檔對(duì)象模型)是針對(duì) HTML 和 XML 文檔的一個(gè) API。DOM 描繪了一個(gè)層次化的節(jié)點(diǎn)樹,允許開發(fā)人員添加、移除和修改頁(yè)面的某一部分。
節(jié)點(diǎn)層次
DOM 可以將任何 HTML 或 XML 文檔描繪成一個(gè)由多層節(jié)點(diǎn)構(gòu)成的結(jié)構(gòu)。節(jié)點(diǎn)分為幾種不同的類型,每種類型分別表示文檔中不同的信息及(或)標(biāo)記。每個(gè)節(jié)點(diǎn)都擁有各自的特點(diǎn)、數(shù)據(jù)和方法,另外也與其他節(jié)點(diǎn)存在某種關(guān)系。節(jié)點(diǎn)之間的關(guān)系構(gòu)成了層次,而所有頁(yè)面標(biāo)記則表現(xiàn)為一個(gè)以特定節(jié)點(diǎn)為根節(jié)點(diǎn)的樹形結(jié)構(gòu)。以下面的 HTML 為例:
<html>
<head>
<title>Sample Page</title>
</head>
<body>
<p>Hello World!</p>
</body>
</html>
可以將這個(gè)簡(jiǎn)單的 HTML 文檔表示為一個(gè)層次結(jié)構(gòu),如圖下圖所示。

在這個(gè)例子中,文檔元素是文檔的最外層元素,文檔中的其他所有元素都包含在文檔元素中。每個(gè)文檔只能有一個(gè)文檔元素。
每一段標(biāo)記都可以通過(guò)樹中的一個(gè)節(jié)點(diǎn)來(lái)表示:HTML 元素通過(guò)元素節(jié)點(diǎn)表示,特性(attribute)通過(guò)特性節(jié)點(diǎn)表示,文檔類型通過(guò)文檔類型節(jié)點(diǎn)表示,而注釋則通過(guò)注釋節(jié)點(diǎn)表示??偣灿?2種節(jié)點(diǎn)類型,這些類型都繼承自一個(gè)基類型。
Node 類型
DOM1 級(jí)定義了一個(gè) Node 接口,該接口將由 DOM 中的所有節(jié)點(diǎn)類型實(shí)現(xiàn)。這個(gè) Node 接口在 JavaScript 中是作為 Node 類型實(shí)現(xiàn)的;除了 IE 之外,在其他所有瀏覽器中都可以訪問到這個(gè)類型。JavaScript 中的所有節(jié)點(diǎn)類型都繼承自 Node 類型,因此所有節(jié)點(diǎn)類型都共享著相同的基本屬性和方法。
每個(gè)節(jié)點(diǎn)都有一個(gè) nodeType 屬性,用于表明節(jié)點(diǎn)的類型。節(jié)點(diǎn)類型由在 Node 類型中定義的下列12個(gè)數(shù)值常量來(lái)表示,任何節(jié)點(diǎn)類型必居其一:
-
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)。
通過(guò)比較上面這些常量,可以很容易地確定節(jié)點(diǎn)的類型,例如:
if (someNode.nodeType == Node.ELEMENT_NODE){ // 在IE中無(wú)效
console.log("Node is an element.");
}
這個(gè)例子比較了 someNode.nodeType 與 Node.ELEMENT_NODE 常量。如果二者相等,則意味著 someNode 確實(shí)是一個(gè)元素。然而,由于 IE 沒有公開 Node 類型的構(gòu)造函數(shù),因此上面的代碼在 IE 中會(huì)導(dǎo)致錯(cuò)誤。為了確??鐬g覽器兼容,最好還是將 nodeType 屬性與數(shù)字值進(jìn)行比較,如下所示:
if (someNode.nodeType == 1){ // 適用于所有瀏覽器
console.log("Node is an element.");
}
并不是所有節(jié)點(diǎn)類型都受到 Web 瀏覽器的支持。開發(fā)人員最常用的就是元素和文本節(jié)點(diǎn)。
Node 屬性概述
Node 常用屬性主要有以下10個(gè),接下來(lái)我們會(huì)著重講解部分屬性。
-
nodeType:顯示節(jié)點(diǎn)的類型 -
nodeName:顯示節(jié)點(diǎn)的名稱 -
nodeValue:顯示節(jié)點(diǎn)的值 -
attributes:獲取一個(gè)屬性節(jié)點(diǎn) -
firstChild:表示某一節(jié)點(diǎn)的第一個(gè)節(jié)點(diǎn) -
lastChild:表示某一節(jié)點(diǎn)的最后一個(gè)子節(jié)點(diǎn) -
childNodes:表示所在節(jié)點(diǎn)的所有子節(jié)點(diǎn) -
parentNode:表示所在節(jié)點(diǎn)的父節(jié)點(diǎn) -
nextSibling:緊挨著當(dāng)前節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn) -
previousSibling:緊挨著當(dāng)前節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn)
nodeName 和 nodeValue 屬性
要了解節(jié)點(diǎn)的具體信息,可以使用 nodeName 和 nodeValue 這兩個(gè)屬性。這兩個(gè)屬性的值完全取決于節(jié)點(diǎn)的類型。在使用這兩個(gè)值以前,最好是像下面這樣先檢測(cè)一下節(jié)點(diǎn)的類型。
if (someNode.nodeType == 1){
value = someNode.nodeName; // nodeName的值是元素的標(biāo)簽名
}
在這個(gè)例子中,首先檢查節(jié)點(diǎn)類型,看它是不是一個(gè)元素。如果是,則取得并保存 nodeName 的值。對(duì)于元素節(jié)點(diǎn),nodeName 中保存的始終都是元素的標(biāo)簽名,而 nodeValue 的值則始終為 null。
節(jié)點(diǎn)關(guān)系
文檔中所有的節(jié)點(diǎn)之間都存在這樣或那樣的關(guān)系。節(jié)點(diǎn)間的各種關(guān)系可以用傳統(tǒng)的家族關(guān)系來(lái)描述,相當(dāng)于把文檔樹比喻成家譜。
每個(gè)節(jié)點(diǎn)都有一個(gè) childNodes 屬性,其中保存著一個(gè) NodeList 對(duì)象。NodeList 是一種類數(shù)組對(duì)象,用于保存一組有序的節(jié)點(diǎn),可以通過(guò)位置來(lái)訪問這些節(jié)點(diǎn)。請(qǐng)注意,雖然可以通過(guò)方括號(hào)語(yǔ)法來(lái)訪問 NodeList 的值,而且這個(gè)對(duì)象也有 length 屬性,但它并不是 Array 的實(shí)例。NodeList 對(duì)象的獨(dú)特之處在于,它實(shí)際上是基于 DOM 結(jié)構(gòu)動(dòng)態(tài)執(zhí)行查詢的結(jié)果,因此 DOM 結(jié)構(gòu)的變化能夠自動(dòng)反映在 NodeList 對(duì)象中。
下面的例子展示了如何訪問保存在 NodeList 中的節(jié)點(diǎn)——可以通過(guò)方括號(hào),也可以使用 item() 方法。
var firstChild = someNode.childNodes[0];
var secondChild = someNode.childNodes.item(1);
var count = someNode.childNodes.length;
無(wú)論使用方括號(hào)還是使用 item() 方法都沒有問題,但使用方括號(hào)語(yǔ)法看起來(lái)與訪問數(shù)組相似,因此頗受一些開發(fā)人員的青睞。另外,要注意 length 屬性表示的是訪問 NodeList 的那一刻,其中包含的節(jié)點(diǎn)數(shù)量。
每個(gè)節(jié)點(diǎn)都有一個(gè) parentNode 屬性,該屬性指向文檔樹中的父節(jié)點(diǎn)。包含在 childNodes 列表中的所有節(jié)點(diǎn)都具有相同的父節(jié)點(diǎn),因此它們的 parentNode 屬性都指向同一個(gè)節(jié)點(diǎn)。此外,包含在 childNodes 列表中的每個(gè)節(jié)點(diǎn)相互之間都是同胞節(jié)點(diǎn)。通過(guò)使用列表中每個(gè)節(jié)點(diǎn)的 previousSibling 和 nextSibling 屬性,可以訪問同一列表中的其他節(jié)點(diǎn)。列表中第一個(gè)節(jié)點(diǎn)的 previousSibling 屬性值為 null,而列表中最后一個(gè)節(jié)點(diǎn)的 nextSibling 屬性的值同樣也為 null,如下面的例子所示:
if (someNode.nextSibling === null){
console.log("Last node in the parent’s childNodes list.");
} else if (someNode.previousSibling === null){
console.log("First node in the parent’s childNodes list.");
}
當(dāng)然,如果列表中只有一個(gè)節(jié)點(diǎn),那么該節(jié)點(diǎn)的 nextSibling 和 previousSibling 都為 null。
父節(jié)點(diǎn)與其第一個(gè)和最后一個(gè)子節(jié)點(diǎn)之間也存在特殊關(guān)系。父節(jié)點(diǎn)的 firstChild 和 lastChild 屬性分別指向其 childNodes 列表中的第一個(gè)和最后一個(gè)節(jié)點(diǎn)。其中,someNode.firstChild 的值始終等于 someNode.childNodes[0],而 someNode.lastChild 的值始終等于 someNode.childNodes [someNode.childNodes.length-1]。在只有一個(gè)子節(jié)點(diǎn)的情況下, firstChild 和 lastChild 指向同一個(gè)節(jié)點(diǎn)。如果沒有子節(jié)點(diǎn),那么 firstChild 和 lastChild 的值均為 null。明確這些關(guān)系能夠?qū)ξ覀儾檎液驮L問文檔結(jié)構(gòu)中的節(jié)點(diǎn)提供極大的便利。下圖形象地展示了上述關(guān)系。

在反映這些關(guān)系的所有屬性當(dāng)中,childNodes 屬性與其他屬性相比更方便一些,因?yàn)橹豁毷褂煤?jiǎn)單的關(guān)系指針,就可以通過(guò)它訪問文檔樹中的任何節(jié)點(diǎn)。另外,hasChildNodes() 也是一個(gè)非常有用的方法,這個(gè)方法在節(jié)點(diǎn)包含一或多個(gè)子節(jié)點(diǎn)的情況下返回 true;應(yīng)該說(shuō),這是比查詢 childNodes 列表的 length 屬性更簡(jiǎn)單的方法。
所有節(jié)點(diǎn)都有的最后一個(gè)屬性是 ownerDocument,該屬性指向表示整個(gè)文檔的文檔節(jié)點(diǎn)。這種關(guān)系表示的是任何節(jié)點(diǎn)都屬于它所在的文檔,任何節(jié)點(diǎn)都不能同時(shí)存在于兩個(gè)或更多個(gè)文檔中。通過(guò)這個(gè)屬性,我們可以不必在節(jié)點(diǎn)層次中通過(guò)層層回溯到達(dá)頂端,而是可以直接訪問文檔節(jié)點(diǎn)。
操作節(jié)點(diǎn)
因?yàn)殛P(guān)系指針都是只讀的,所以 DOM 提供了一些操作節(jié)點(diǎn)的方法。其中,最常用的方法是 appendChild(),用于向 childNodes 列表的末尾添加一個(gè)節(jié)點(diǎn)。添加節(jié)點(diǎn)后,childNodes 的新增節(jié)點(diǎn)、父節(jié)點(diǎn)及以前的最后一個(gè)子節(jié)點(diǎn)的關(guān)系指針都會(huì)相應(yīng)地得到更新。更新完成后,appendChild() 返回新增的節(jié)點(diǎn)。來(lái)看下面的例子:
var returnedNode = someNode.appendChild(newNode);
console.log(returnedNode == newNode); // true
console.log(someNode.lastChild == newNode); // true
如果傳入到 appendChild() 中的節(jié)點(diǎn)已經(jīng)是文檔的一部分了,那結(jié)果就是將該節(jié)點(diǎn)從原來(lái)的位置轉(zhuǎn)移到新位置。即使可以將 DOM 樹看成是由一系列指針連接起來(lái)的,但任何 DOM 節(jié)點(diǎn)也不能同時(shí)出現(xiàn)在文檔中的多個(gè)位置上。因此,如果在調(diào)用 appendChild() 時(shí)傳入了父節(jié)點(diǎn)的第一個(gè)子節(jié)點(diǎn),那么該節(jié)點(diǎn)就會(huì)成為父節(jié)點(diǎn)的最后一個(gè)子節(jié)點(diǎn),如下面的例子所示。
// someNode 有多個(gè)子節(jié)點(diǎn)
var returnedNode = someNode.appendChild(someNode.firstChild);
console.log(returnedNode == someNode.firstChild); // false
console.log(returnedNode == someNode.lastChild); // true
如果需要把節(jié)點(diǎn)放在 childNodes 列表中某個(gè)特定的位置上,而不是放在末尾,那么可以使用 insertBefore() 方法。這個(gè)方法接受兩個(gè)參數(shù):要插入的節(jié)點(diǎn)和作為參照的節(jié)點(diǎn)。插入節(jié)點(diǎn)后,被插入的節(jié)點(diǎn)會(huì)變成參照節(jié)點(diǎn)的前一個(gè)同胞節(jié)點(diǎn) previousSibling,同時(shí)被方法返回。如果參照節(jié)點(diǎn)是 null,則 insertBefore() 與 appendChild() 執(zhí)行相同的操作,如下面的例子所示。
// 插入后成為最后一個(gè)子節(jié)點(diǎn)
returnedNode = someNode.insertBefore(newNode, null);
console.log(newNode == someNode.lastChild); // true
// 插入后成為第一個(gè)子節(jié)點(diǎn)
var returnedNode = someNode.insertBefore(newNode, someNode.firstChild);
console.log(returnedNode == newNode); // true
console.log(newNode == someNode.firstChild); // true
// 插入到最后一個(gè)子節(jié)點(diǎn)前面
returnedNode = someNode.insertBefore(newNode, someNode.lastChild);
console.log(newNode == someNode.childNodes[someNode.childNodes.length-2]); // true
前面介紹的 appendChild() 和 insertBefore() 方法都只插入節(jié)點(diǎn),不會(huì)移除節(jié)點(diǎn)。而下面要介紹的 replaceChild() 方法接受的兩個(gè)參數(shù)是:要插入的節(jié)點(diǎn)和要替換的節(jié)點(diǎn)。要替換的節(jié)點(diǎn)將由這個(gè)方法返回并從文檔樹中被移除,同時(shí)由要插入的節(jié)點(diǎn)占據(jù)其位置。來(lái)看下面的例子。
// 替換第一個(gè)子節(jié)點(diǎn)
var returnedNode = someNode.replaceChild(newNode, someNode.firstChild);
// 替換最后一個(gè)子節(jié)點(diǎn)
returnedNode = someNode.replaceChild(newNode, someNode.lastChild);
在使用 replaceChild() 插入一個(gè)節(jié)點(diǎn)時(shí),該節(jié)點(diǎn)的所有關(guān)系指針都會(huì)從被它替換的節(jié)點(diǎn)復(fù)制過(guò)來(lái)。盡管從技術(shù)上講,被替換的節(jié)點(diǎn)仍然還在文檔中,但它在文檔中已經(jīng)沒有了自己的位置。
如果只想移除而非替換節(jié)點(diǎn),可以使用 removeChild() 方法。這個(gè)方法接受一個(gè)參數(shù),即要移除的節(jié)點(diǎn)。被移除的節(jié)點(diǎn)將成為方法的返回值,如下面的例子所示。
// 移除第一個(gè)子節(jié)點(diǎn)
var formerFirstChild = someNode.removeChild(someNode.firstChild);
// 移除最后一個(gè)子節(jié)點(diǎn)
var formerLastChild = someNode.removeChild(someNode.lastChild);
與使用 replaceChild() 方法一樣,通過(guò) removeChild() 移除的節(jié)點(diǎn)仍然為文檔所有,只不過(guò)在文檔中已經(jīng)沒有了自己的位置。
前面介紹的四個(gè)方法操作的都是某個(gè)節(jié)點(diǎn)的子節(jié)點(diǎn),也就是說(shuō),要使用這幾個(gè)方法必須先取得父節(jié)點(diǎn)(使用 parentNode 屬性)。另外,并不是所有類型的節(jié)點(diǎn)都有子節(jié)點(diǎn),如果在不支持子節(jié)點(diǎn)的節(jié)點(diǎn)上調(diào)用了這些方法,將會(huì)導(dǎo)致錯(cuò)誤發(fā)生。
Document 類型
JavaScript 通過(guò) Document 類型表示文檔。在瀏覽器中,document 對(duì)象是 HTMLDocument(繼承自 Document 類型)的一個(gè)實(shí)例,表示整個(gè) HTML 頁(yè)面。而且,document 對(duì)象是 window 對(duì)象的一個(gè)屬性,因此可以將其作為全局對(duì)象來(lái)訪問。Document 節(jié)點(diǎn)具有下列特征:
-
nodeType的值為9; -
nodeName的值為"#document"; -
nodeValue的值為null; -
parentNode的值為null; -
ownerDocument的值為null; - 其子節(jié)點(diǎn)可能是一個(gè)
DocumentType(最多一個(gè))、Element(最多一個(gè))、ProcessingInstruction或Comment。
Document 類型可以表示 HTML 頁(yè)面或者其他基于 XML 的文檔。不過(guò),最常見的應(yīng)用還是作為 HTMLDocument 實(shí)例的 document 對(duì)象。通過(guò)這個(gè)文檔對(duì)象,不僅可以取得與頁(yè)面有關(guān)的信息,而且還能操作頁(yè)面的外觀及其底層結(jié)構(gòu)。
文檔的子節(jié)點(diǎn)
雖然 DOM 標(biāo)準(zhǔn)規(guī)定 Document 節(jié)點(diǎn)的子節(jié)點(diǎn)可以是DocumentType、Element、ProcessingInstruction 或 Comment,但還有兩個(gè)內(nèi)置的訪問其子節(jié)點(diǎn)的快捷方式。第一個(gè)就是documentElement 屬性,該屬性始終指向 HTML 頁(yè)面中的 html 元素。另一個(gè)就是通過(guò) childNodes 列表訪問文檔元素,但通過(guò) documentElement 屬性則能更快捷、更直接地訪問該元素。以下面這個(gè)簡(jiǎn)單的頁(yè)面為例。
<html>
<body>
</body>
</html>
這個(gè)頁(yè)面在經(jīng)過(guò)瀏覽器解析后,其文檔中只包含一個(gè)子節(jié)點(diǎn),即 html 元素。可以通過(guò) documentElement 或 childNodes 列表來(lái)訪問這個(gè)元素,如下所示。
var html = document.documentElement; // 取得對(duì)<html>的引用
console.log(html === document.childNodes[0]); // true
console.log(html === document.firstChild); // true
這個(gè)例子說(shuō)明,documentElement、firstChild 和 childNodes[0] 的值相同,都指向 <html> 元素。
作為 HTMLDocument 的實(shí)例,document 對(duì)象還有一個(gè) body 屬性,直接指向 <body> 元素。因?yàn)殚_發(fā)人員經(jīng)常要使用這個(gè)元素,所以 document.body 在 JavaScript 代碼中出現(xiàn)的頻率非常高,其用法如下。
var body = document.body; // 取得對(duì)<body>的引用
所有瀏覽器都支持 document.documentElement 和 document.body 屬性。
Document 另一個(gè)可能的子節(jié)點(diǎn)是 DocumentType。通常將 <!DOCTYPE> 標(biāo)簽看成一個(gè)與文檔其他部分不同的實(shí)體,可以通過(guò) doctype 屬性(在瀏覽器中是 document.doctype )來(lái)訪問它的信息。
var doctype = document.doctype; // 取得對(duì)<!DOCTYPE>的引用
瀏覽器對(duì) document.doctype 的支持差別很大,可以給出如下總結(jié)。
- IE8 及之前版本:如果存在文檔類型聲明,會(huì)將其錯(cuò)誤地解釋為一個(gè)注釋并把它當(dāng)作
Comment節(jié)點(diǎn);而document.doctype的值始終為null。 - IE9+ 及 Firefox:如果存在文檔類型聲明,則將其作為文檔的第一個(gè)子節(jié)點(diǎn);
document.doctype是一個(gè)DocumentType節(jié)點(diǎn),也可以通過(guò)document.firstChild或document.childNodes[0]訪問同一個(gè)節(jié)點(diǎn)。 - Safari、Chrome 和 Opera:如果存在文檔類型聲明,則將其解析,但不作為文檔的子節(jié)點(diǎn)。
document.doctype是一個(gè)DocumentType節(jié)點(diǎn),但該節(jié)點(diǎn)不會(huì)出現(xiàn)在document.childNodes中。
由于瀏覽器對(duì) document.doctype 的支持不一致,因此這個(gè)屬性的用處很有限。
文檔信息
作為 HTMLDocument 的一個(gè)實(shí)例,document 對(duì)象還有一些標(biāo)準(zhǔn)的 Document 對(duì)象所沒有的屬性。這些屬性提供了 document 對(duì)象所表現(xiàn)的網(wǎng)頁(yè)的一些信息。其中第一個(gè)屬性就是 title,包含著 <title> 元素中的文本——顯示在瀏覽器窗口的標(biāo)題欄或標(biāo)簽頁(yè)上。通過(guò)這個(gè)屬性可以取得當(dāng)前頁(yè)面的標(biāo)題,也可以修改當(dāng)前頁(yè)面的標(biāo)題并反映在瀏覽器的標(biāo)題欄中。
// 取得文檔標(biāo)題
var originalTitle = document.title;
// 設(shè)置文檔標(biāo)題
document.title = "New page title";
接下來(lái)要介紹的3個(gè)屬性都與對(duì)網(wǎng)頁(yè)的請(qǐng)求有關(guān),它們是 URL、domain 和 referrer。URL 屬性中包含頁(yè)面完整的 URL(即地址欄中顯示的URL),domain 屬性中只包含頁(yè)面的域名,而 referrer 屬性中則保存著鏈接到當(dāng)前頁(yè)面的那個(gè)頁(yè)面的 URL。在沒有來(lái)源頁(yè)面的情況下,referrer 屬性中可能會(huì)包含空字符串。所有這些信息都存在于請(qǐng)求的 HTTP 頭部,只不過(guò)是通過(guò)這些屬性讓我們能夠在 JavaScrip 中訪問它們而已,如下面的例子所示。
// 取得完整的URL
var url = document.URL;
// 取得域名
var domain = document.domain;
// 取得來(lái)源頁(yè)面的URL
var referrer = document.referrer;
查找元素
說(shuō)到最常見的 DOM 應(yīng)用,恐怕就要數(shù)取得特定的某個(gè)或某組元素的引用,然后再執(zhí)行一些操作了。取得元素的操作可以使用 document 對(duì)象的幾個(gè)方法來(lái)完成。其中,Document 類型為此提供了兩個(gè)方法:getElementById() 和 getElementsByTagName()。
第一個(gè)方法,getElementById(),接收一個(gè)參數(shù):要取得的元素的 ID。如果找到相應(yīng)的元素則返回該元素,如果不存在帶有相應(yīng) ID 的元素,則返回 null。注意,這里的 ID 必須與頁(yè)面中元素的 id 特性(attribute)嚴(yán)格匹配,包括大小寫。以下面的元素為例。
<div id="myDiv">Some text</div>
可以使用下面的代碼取得這個(gè)元素:
var div = document.getElementById("myDiv"); // 取得<div>元素的引用
但是,下面的代碼在除 IE7 及更早版本之外的所有瀏覽器中都將返回 null。
var div = document.getElementById("mydiv"); // 無(wú)效的ID(在IE7及更早版本中可以)
IE8 及較低版本不區(qū)分 ID 的大小寫,因此 "myDiv" 和 "mydiv" 會(huì)被當(dāng)作相同的元素 ID。如果頁(yè)面中多個(gè)元素的ID值相同,getElementById() 只返回文檔中第一次出現(xiàn)的元素。
另一個(gè)常用于取得元素引用的方法是 getElementsByTagName()。這個(gè)方法接受一個(gè)參數(shù),即要取得元素的標(biāo)簽名,而返回的是包含零或多個(gè)元素的 NodeList。在HTML文檔中,這個(gè)方法會(huì)返回一個(gè)HTMLCollection 對(duì)象,作為一個(gè)“動(dòng)態(tài)”集合,該對(duì)象與 NodeList非常類似。例如,下列代碼會(huì)取得頁(yè)面中所有的 <img> 元素,并返回一個(gè) HTMLCollection。
var images = document.getElementsByTagName("img");
這行代碼會(huì)將一個(gè) HTMLCollection 對(duì)象保存在 images 變量中。與 NodeList 對(duì)象類似,可以使用方括號(hào)語(yǔ)法或 item() 方法來(lái)訪問 HTMLCollection 對(duì)象中的項(xiàng)。而這個(gè)對(duì)象中元素的數(shù)量則可以通過(guò)其 length 屬性取得,如下面的例子所示。
console.log(images.length); // 輸出圖像的數(shù)量
console.log(images[0].src); // 輸出第一個(gè)圖像元素的src特性
console.log(images.item(0).src); // 輸出第一個(gè)圖像元素的src特性
HTMLCollection 對(duì)象還有一個(gè)方法,叫做 namedItem(),使用這個(gè)方法可以通過(guò)元素的 name 特性取得集合中的項(xiàng)。例如,假設(shè)上面提到的頁(yè)面中包含如下 <img> 元素:
[站外圖片上傳中……(3)]
那么就可以通過(guò)如下方式從 images 變量中取得這個(gè) <img> 元素:
var myImage = images.namedItem("myImage");
在提供按索引訪問項(xiàng)的基礎(chǔ)上,HTMLCollection 還支持按名稱訪問項(xiàng),這就為我們?nèi)〉脤?shí)際想要的元素提供了便利。而且,對(duì)命名的項(xiàng)也可以使用方括號(hào)語(yǔ)法來(lái)訪問,如下所示:
var myImage = images["myImage"];
對(duì) HTMLCollection 而言,我們可以向方括號(hào)中傳入數(shù)值或字符串形式的索引值。在后臺(tái),對(duì)數(shù)值索引就會(huì)調(diào)用 item(),而對(duì)字符串索引就會(huì)調(diào)用 namedItem()。
要想取得文檔中的所有元素,可以向 getElementsByTagName() 中傳入 "*"。在 JavaScript 及 CSS 中,星號(hào)(*)通常表示“全部”。下面看一個(gè)例子。
var allElements = document.getElementsByTagName("*");
僅此一行代碼返回的 HTMLCollection 中,就包含了整個(gè)頁(yè)面中的所有元素——按照它們出現(xiàn)的先后順序。換句話說(shuō),第一項(xiàng)是 <html> 元素,第二項(xiàng)是 <head> 元素,以此類推。由于 IE 將注釋(Comment)實(shí)現(xiàn)為元素(Element),因此在IE中調(diào)用 getElementsByTagName("*") 將會(huì)返回所有注釋節(jié)點(diǎn)。
第三個(gè)方法,也是只有 HTMLDocument 類型才有的方法,是 getElementsByName()。顧名思義,這個(gè)方法會(huì)返回帶有給定 name 特性的所有元素。最常使用 getElementsByName() 方法的情況是取得單選按鈕;為了確保發(fā)送給瀏覽器的值正確無(wú)誤,所有單選按鈕必須具有相同的 name 特性,如下面的例子所示。
<fieldset>
<legend>Which color do you prefer?</legend>
<ul>
<li><input type="radio" value="red" name="color" id="colorRed">
<label for="colorRed">Red</label></li>
<li><input type="radio" value="green" name="color" id="colorGreen">
<label for="colorGreen">Green</label></li>
<li><input type="radio" value="blue" name="color" id="colorBlue">
<label for="colorBlue">Blue</label></li>
</ul>
</fieldset>
如這個(gè)例子所示,其中所有單選按鈕的 name 特性值都是 "color",但它們的 ID 可以不同。ID 的作用在于將 <label> 元素應(yīng)用到每個(gè)單選按鈕,而 name 特性則用以確保三個(gè)值中只有一個(gè)被發(fā)送給瀏覽器。這樣,我們就可以使用如下代碼取得所有單選按鈕:
var radios = document.getElementsByName("color");
與 getElementsByTagName() 類似,getElementsByName() 方法也會(huì)返回一個(gè) HTMLCollectioin。但是,對(duì)于這里的單選按鈕來(lái)說(shuō),namedItem() 方法則只會(huì)取得第一項(xiàng)(因?yàn)槊恳豁?xiàng)的 name 特性都相同)。
特殊集合
除了屬性和方法,document 對(duì)象還有一些特殊的集合。這些集合都是 HTMLCollection 對(duì)象,為訪問文檔常用的部分提供了快捷方式,包括:
-
document.anchors,包含文檔中所有帶name特性的<a>元素; -
document.applets,包含文檔中所有的<applet>元素,因?yàn)椴辉偻扑]使用<applet>元素,所以這個(gè)集合已經(jīng)不建議使用了; -
document.forms,包含文檔中所有的<form>元素,與document.getElementsByTagName("form")得到的結(jié)果相同; -
document.images,包含文檔中所有的<img>元素,與document.getElementsByTagName("img")得到的結(jié)果相同; -
document.links,包含文檔中所有帶href特性的<a>元素。
這個(gè)特殊集合始終都可以通過(guò) HTMLDocument 對(duì)象訪問到,而且,與 HTMLCollection 對(duì)象類似,集合中的項(xiàng)也會(huì)隨著當(dāng)前文檔內(nèi)容的更新而更新。
文檔寫入
有一個(gè) document 對(duì)象的功能已經(jīng)存在很多年了,那就是將輸出流寫入到網(wǎng)頁(yè)中的能力。這個(gè)能力體現(xiàn)在下列4個(gè)方法中:write()、writeln()、open() 和 close()。其中,write() 和 writeln() 方法都接受一個(gè)字符串參數(shù),即要寫入到輸出流中的文本。write() 會(huì)原樣寫入,而 writeln() 則會(huì)在字符串的末尾添加一個(gè)換行符 \n。在頁(yè)面被加載的過(guò)程中,可以使用這兩個(gè)方法向頁(yè)面中動(dòng)態(tài)地加入內(nèi)容,如下面的例子所示。
<html>
<head>
<title>document.write() Example</title>
</head>
<body>
<p>The current date and time is:
<script type="text/javascript">
document.write("<strong>" + (new Date()).toString() + "</strong>");
</script>
</p>
</body>
</html>
這個(gè)例子展示了在頁(yè)面加載過(guò)程中輸出當(dāng)前日期和時(shí)間的代碼。其中,日期被包含在一個(gè) <strong> 元素中,就像在 HTML 頁(yè)面中包含普通的文本一樣。這樣做會(huì)創(chuàng)建一個(gè) DOM 元素,而且可以在將來(lái)訪問該元素。通過(guò) write() 和 writeln() 輸出的任何 HTML 代碼都將如此處理。
此外,還可以使用 write() 和 writeln() 方法動(dòng)態(tài)地包含外部資源,例如 JavaScript 文件等。在包含 JavaScript 文件時(shí),必須注意不能像下面的例子那樣直接包含字符串 "</script>",因?yàn)檫@會(huì)導(dǎo)致該字符串被解釋為腳本塊的結(jié)束,它后面的代碼將無(wú)法執(zhí)行。
<html>
<head>
<title>document.write() Example 2</title>
</head>
<body>
<script type="text/javascript">
document.write("<script type=\"text/javascript\" src=\"file.js\">" +
"</script>");
</script>
</body>
</html>
即使這個(gè)文件看起來(lái)沒錯(cuò),但字符串 "</script>" 將被解釋為與外部的 <script> 標(biāo)簽匹配,結(jié)果文本 ");將會(huì)出現(xiàn)在頁(yè)面中。為避免這個(gè)問題,只需把這個(gè)字符串分開寫即可;第2章也曾經(jīng)提及這個(gè)問題,解決方案如下。
<html>
<head>
<title>document.write() Example 3</title>
</head>
<body>
<script type="text/javascript">
document.write("<script type=\"text/javascript\" src=\"file.js\">" +
"<\/script>");
</script>
</body>
</html>
字符串 "<\/script>" 不會(huì)被當(dāng)作外部 <script> 標(biāo)簽的關(guān)閉標(biāo)簽,因而頁(yè)面中也就不會(huì)出現(xiàn)多余的內(nèi)容了。
前面的例子使用 document.write() 在頁(yè)面被呈現(xiàn)的過(guò)程中直接向其中輸出了內(nèi)容。如果在文檔加載結(jié)束后再調(diào)用 document.write(),那么輸出的內(nèi)容將會(huì)重寫整個(gè)頁(yè)面,如下面的例子所示:
<html>
<head>
<title>document.write() Example 4</title>
</head>
<body>
<p>This is some content that you won't get to see because it will be overwritten.</p>
<script type="text/javascript">
window.onload = function(){
document.write("Hello world!");
};
</script>
</body>
</html>
在這個(gè)例子中,我們使用了 window.onload 事件處理程序,等到頁(yè)面完全加載之后延遲執(zhí)行函數(shù)。函數(shù)執(zhí)行之后,字符串 "Hello world!" 會(huì)重寫整個(gè)頁(yè)面內(nèi)容。
方法 open() 和 close() 分別用于打開和關(guān)閉網(wǎng)頁(yè)的輸出流。如果是在頁(yè)面加載期間使用 write() 或 writeln() 方法,則不需要用到這兩個(gè)方法。
關(guān)卡
仔細(xì)想想,下面代碼塊會(huì)輸出什么結(jié)果呢?
<!-- 挑戰(zhàn)一 -->
<body>
<div id = "t"><span>aaa</span><span>bbb</span><span>ccc</span></div>
</body>
<script>
var d = document.getElementById("t");
document.writeln(d.firstChild.innerHTML); // ???
document.writeln(d.lastChild.innerHTML); // ???
</script>
<!-- 挑戰(zhàn)二 -->
<body name="ddd">
<div id = "t"><span>aaa</span><span>bbb</span><span>ccc</span></div>
</body>
<script>
var d = document.getElementById("t");
document.writeln(d.childNodes[1].innerHTML); // ???
document.writeln(d.parentNode.getAttribute("name")); // ???
</script>
<!-- 挑戰(zhàn)三 -->
<body name="ddd">
<div id = "t"><span>aaa</span><span>bbb</span><span>ccc</span></div>
</body>
<script>
var d = document.getElementById("t").childNodes[1];
document.writeln(d.nextSibling.innerHTML); // ???
document.writeln(d.previousSibling.innerHTML); // ???
</script>
更多
關(guān)注微信公眾號(hào)「劼哥舍」回復(fù)「答案」,獲取關(guān)卡詳解。
關(guān)注 https://github.com/stone0090/javascript-lessons,獲取最新動(dòng)態(tài)。