《JavaScript 闖關(guān)記》之 DOM(上)

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.nodeTypeNode.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)

nodeNamenodeValue 屬性

要了解節(jié)點(diǎn)的具體信息,可以使用 nodeNamenodeValue 這兩個(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)的 previousSiblingnextSibling 屬性,可以訪問同一列表中的其他節(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)的 nextSiblingpreviousSibling 都為 null。

父節(jié)點(diǎn)與其第一個(gè)和最后一個(gè)子節(jié)點(diǎn)之間也存在特殊關(guān)系。父節(jié)點(diǎn)的 firstChildlastChild 屬性分別指向其 childNodes 列表中的第一個(gè)和最后一個(gè)節(jié)點(diǎn)。其中,someNode.firstChild 的值始終等于 someNode.childNodes[0],而 someNode.lastChild 的值始終等于 someNode.childNodes [someNode.childNodes.length-1]。在只有一個(gè)子節(jié)點(diǎn)的情況下, firstChildlastChild 指向同一個(gè)節(jié)點(diǎn)。如果沒有子節(jié)點(diǎn),那么 firstChildlastChild 的值均為 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è))、ProcessingInstructionComment

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、ElementProcessingInstructionComment,但還有兩個(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ò) documentElementchildNodes 列表來(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、firstChildchildNodes[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.documentElementdocument.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.firstChilddocument.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、domainreferrer。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)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容