JavaScript Node節(jié)點(diǎn)

2021-09-15 15:14 更新

DOM的概念

DOM是文檔對(duì)象模型(Document Object Model)的簡稱,它的基本思想是把結(jié)構(gòu)化文檔(比如HTML和XML)解析成一系列的節(jié)點(diǎn),再由這些節(jié)點(diǎn)組成一個(gè)樹狀結(jié)構(gòu)(DOM Tree)。所有的節(jié)點(diǎn)和最終的樹狀結(jié)構(gòu),都有規(guī)范的對(duì)外接口,以達(dá)到使用編程語言操作文檔的目的(比如增刪內(nèi)容)。所以,DOM可以理解成文檔(HTML文檔、XML文檔和SVG文檔)的編程接口。

DOM有自己的國際標(biāo)準(zhǔn),目前的通用版本是DOM 3,下一代版本DOM 4正在擬定中。本章介紹的就是JavaScript對(duì)DOM標(biāo)準(zhǔn)的實(shí)現(xiàn)和用法。

嚴(yán)格地說,DOM不屬于JavaScript,但是操作DOM是JavaScript最常見的任務(wù),而JavaScript也是最常用于DOM操作的語言。所以,DOM往往放在JavaScript里面介紹。

節(jié)點(diǎn)的概念

DOM的最小組成單位叫做節(jié)點(diǎn)(node),一個(gè)文檔的樹形結(jié)構(gòu)(DOM樹),就是由各種不同類型的節(jié)點(diǎn)組成。

對(duì)于HTML文檔,節(jié)點(diǎn)主要有以下六種類型:Document節(jié)點(diǎn)、DocumentType節(jié)點(diǎn)、Element節(jié)點(diǎn)、Attribute節(jié)點(diǎn)、Text節(jié)點(diǎn)和DocumentFragment節(jié)點(diǎn)。

節(jié)點(diǎn) 名稱 含義
Document 文檔節(jié)點(diǎn) 整個(gè)文檔(window.document)
DocumentType 文檔類型節(jié)點(diǎn) 文檔的類型(比如)
Element 元素節(jié)點(diǎn) HTML元素(比如、等)
Attribute 屬性節(jié)點(diǎn) HTML元素的屬性(比如class="right")
Text 文本節(jié)點(diǎn) HTML文檔中出現(xiàn)的文本
DocumentFragment 文檔碎片節(jié)點(diǎn) 文檔的片段

瀏覽器原生提供一個(gè)Node對(duì)象,上表所有類型的節(jié)點(diǎn)都是Node對(duì)象派生出來的。也就是說,它們都繼承了Node的屬性和方法。

Node節(jié)點(diǎn)的屬性

nodeName,nodeType

nodeName屬性返回節(jié)點(diǎn)的名稱,nodeType屬性返回節(jié)點(diǎn)的常數(shù)值。具體的返回值,可查閱下方的表格。

類型 nodeName nodeType
DOCUMENT_NODE #document 9
ELEMENT_NODE 大寫的HTML元素名 1
ATTRIBUTE_NODE 等同于Attr.name 2
TEXT_NODE #text 3
DOCUMENT_FRAGMENT_NODE #document-fragment 11
DOCUMENT_TYPE_NODE 等同于DocumentType.name 10

以document節(jié)點(diǎn)為例,它的nodeName屬性等于#document,nodeType屬性等于9。

document.nodeName // "#document"
document.nodeType // 9

通常來說,使用nodeType屬性確定一個(gè)節(jié)點(diǎn)的類型,比較方便。

document.querySelector('a').nodeType === 1
// true

document.querySelector('a').nodeType === Node.ELEMENT_NODE
// true

上面兩種寫法是等價(jià)的。

ownerDocument,nextSibling,previousSibling,parentNode,parentElement

以下屬性返回當(dāng)前節(jié)點(diǎn)的相關(guān)節(jié)點(diǎn)。

(1)ownerDocument

ownerDocument屬性返回當(dāng)前節(jié)點(diǎn)所在的頂層文檔對(duì)象,即document對(duì)象。

var d = p.ownerDocument;
d === document // true

document對(duì)象本身的ownerDocument屬性,返回null。

(2)nextSibling

nextsibling屬性返回緊跟在當(dāng)前節(jié)點(diǎn)后面的第一個(gè)同級(jí)節(jié)點(diǎn)。如果當(dāng)前節(jié)點(diǎn)后面沒有同級(jí)節(jié)點(diǎn),則返回null。注意,該屬性還包括文本節(jié)點(diǎn)和評(píng)論節(jié)點(diǎn)。因此如果當(dāng)前節(jié)點(diǎn)后面有空格,該屬性會(huì)返回一個(gè)文本節(jié)點(diǎn),內(nèi)容為空格。

var el = document.getelementbyid('div-01').firstchild;
var i = 1;

while (el) {
  console.log(i + '. ' + el.nodename);
  el = el.nextsibling;
  i++;
}

上面代碼遍歷div-01節(jié)點(diǎn)的所有子節(jié)點(diǎn)。

(3)previousSibling

previoussibling屬性返回當(dāng)前節(jié)點(diǎn)前面的、距離最近的一個(gè)同級(jí)節(jié)點(diǎn)。如果當(dāng)前節(jié)點(diǎn)前面沒有同級(jí)節(jié)點(diǎn),則返回null。

// html代碼如下
// <a><b1 id="b1"/><b2 id="b2"/></a>

document.getelementbyid("b1").previoussibling // null
document.getelementbyid("b2").previoussibling.id // "b1"

對(duì)于當(dāng)前節(jié)點(diǎn)前面有空格,則previoussibling屬性會(huì)返回一個(gè)內(nèi)容為空格的文本節(jié)點(diǎn)。

(4)parentNode

parentNode屬性返回當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)。對(duì)于一個(gè)節(jié)點(diǎn)來說,它的父節(jié)點(diǎn)只可能是三種類型:element節(jié)點(diǎn)、document節(jié)點(diǎn)和documentfragment節(jié)點(diǎn)。

下面代碼是如何從父節(jié)點(diǎn)移除指定節(jié)點(diǎn)。

if (node.parentNode) {
  node.parentNode.removeChild(node);
}

對(duì)于document節(jié)點(diǎn)和documentfragment節(jié)點(diǎn),它們的父節(jié)點(diǎn)都是null。另外,對(duì)于那些生成后還沒插入DOM樹的節(jié)點(diǎn),父節(jié)點(diǎn)也是null。

(5)parentElement

parentElement屬性返回當(dāng)前節(jié)點(diǎn)的父Element節(jié)點(diǎn)。如果當(dāng)前節(jié)點(diǎn)沒有父節(jié)點(diǎn),或者父節(jié)點(diǎn)類型不是Element節(jié)點(diǎn),則返回null。

if (node.parentElement) {
  node.parentElement.style.color = "red";
}

上面代碼設(shè)置指定節(jié)點(diǎn)的父Element節(jié)點(diǎn)的CSS屬性。

在IE瀏覽器中,只有Element節(jié)點(diǎn)才有該屬性,其他瀏覽器則是所有類型的節(jié)點(diǎn)都有該屬性。

textContent,nodeValue

以下屬性返回當(dāng)前節(jié)點(diǎn)的內(nèi)容。

(1)textContent

textContent屬性返回當(dāng)前節(jié)點(diǎn)和它的所有后代節(jié)點(diǎn)的文本內(nèi)容。

// HTML代碼為
// <div id="divA">This is <span>some</span> text</div>

document.getElementById("divA").textContent
// This is some text

上面代碼的textContent屬性,自動(dòng)忽略當(dāng)前節(jié)點(diǎn)內(nèi)部的HTML標(biāo)簽,返回所有文本內(nèi)容。

該屬性是可讀寫的,設(shè)置該屬性的值,會(huì)用一個(gè)新的文本節(jié)點(diǎn),替換所有它原來的子節(jié)點(diǎn)。它還有一個(gè)好處,就是自動(dòng)對(duì)HTML標(biāo)簽轉(zhuǎn)義。這很適合用于用戶提供的內(nèi)容。

document.getElementById('foo').textContent = '<p>GoodBye!</p>';

上面代碼在插入文本時(shí),會(huì)將p標(biāo)簽解釋為文本,即<p>,而不會(huì)當(dāng)作標(biāo)簽處理。

對(duì)于Text節(jié)點(diǎn)和Comment節(jié)點(diǎn),該屬性的值與nodeValue屬性相同。對(duì)于其他類型的節(jié)點(diǎn),該屬性會(huì)將每個(gè)子節(jié)點(diǎn)的內(nèi)容連接在一起返回,但是不包括Comment節(jié)點(diǎn)。如果一個(gè)節(jié)點(diǎn)沒有子節(jié)點(diǎn),則返回空字符串。

document節(jié)點(diǎn)和doctype節(jié)點(diǎn)的textContent屬性為null。如果要讀取整個(gè)文檔的內(nèi)容,可以使用document.documentElement.textContent

在IE瀏覽器,所有Element節(jié)點(diǎn)都有一個(gè)innerText屬性。它與textContent屬性基本相同,但是有幾點(diǎn)區(qū)別。

  • innerText受CSS影響,textcontent不受。比如,如果CSS規(guī)則隱藏(hidden)了某段文本,innerText就不會(huì)返回這段文本,textcontent則照樣返回。

  • innerText返回的文本,會(huì)過濾掉空格、換行和回車鍵,textcontent則不會(huì)。

  • innerText屬性不是DOM標(biāo)準(zhǔn)的一部分,F(xiàn)irefox瀏覽器甚至沒有部署這個(gè)屬性,而textcontent是DOM標(biāo)準(zhǔn)的一部分。

(2)nodeValue

nodeValue屬性返回或設(shè)置當(dāng)前節(jié)點(diǎn)的值,格式為字符串。但是,該屬性只對(duì)Text節(jié)點(diǎn)、Comment節(jié)點(diǎn)、XML文檔的CDATA節(jié)點(diǎn)有效,其他類型的節(jié)點(diǎn)一律返回null。

因此,nodeValue屬性一般只用于Text節(jié)點(diǎn)。對(duì)于那些返回null的節(jié)點(diǎn),設(shè)置nodeValue屬性是無效的。

childNodes,firstNode,lastChild

以下屬性返回當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn)。

(1)childNodes

childNodes屬性返回一個(gè)NodeList集合,成員包括當(dāng)前節(jié)點(diǎn)的所有子節(jié)點(diǎn)。注意,除了HTML元素節(jié)點(diǎn),該屬性返回的還包括Text節(jié)點(diǎn)和Comment節(jié)點(diǎn)。如果當(dāng)前節(jié)點(diǎn)不包括任何子節(jié)點(diǎn),則返回一個(gè)空的NodeList集合。由于NodeList對(duì)象是一個(gè)動(dòng)態(tài)集合,一旦子節(jié)點(diǎn)發(fā)生變化,立刻會(huì)反映在返回結(jié)果之中。

var ulElementChildNodes = document.querySelector('ul').childNodes;

(2)firstNode

firstNode屬性返回當(dāng)前節(jié)點(diǎn)的第一個(gè)子節(jié)點(diǎn),如果當(dāng)前節(jié)點(diǎn)沒有子節(jié)點(diǎn),則返回null。注意,除了HTML元素子節(jié)點(diǎn),該屬性還包括文本節(jié)點(diǎn)和評(píng)論節(jié)點(diǎn)。

(3)lastChild

lastChild屬性返回當(dāng)前節(jié)點(diǎn)的最后一個(gè)子節(jié)點(diǎn),如果當(dāng)前節(jié)點(diǎn)沒有子節(jié)點(diǎn),則返回null。

baseURI

baseURI屬性返回一個(gè)字符串,由當(dāng)前網(wǎng)頁的協(xié)議、域名和所在的目錄組成,表示當(dāng)前網(wǎng)頁的絕對(duì)路徑。如果無法取到這個(gè)值,則返回null。瀏覽器根據(jù)這個(gè)屬性,計(jì)算網(wǎng)頁上的相對(duì)路徑的URL。該屬性為只讀。

通常情況下,該屬性由當(dāng)前網(wǎng)址的URL(即window.location屬性)決定,但是可以使用HTML的標(biāo)簽,改變?cè)搶傩缘闹怠?/p>

<base  rel="external nofollow" target="_blank"  rel="external nofollow" target="_blank" >
<base target="_blank"  rel="external nofollow" target="_blank"  rel="external nofollow" target="_blank" >

該屬性不僅document對(duì)象有(document.baseURI),元素節(jié)點(diǎn)也有(element.baseURI)。通常情況下,它們的值是相同的。

Node節(jié)點(diǎn)的方法

appendChild(),hasChildNodes()

以下方法與子節(jié)點(diǎn)相關(guān)。

(1)appendChild()

appendChild方法接受一個(gè)節(jié)點(diǎn)對(duì)象作為參數(shù),將其作為最后一個(gè)子節(jié)點(diǎn),插入當(dāng)前節(jié)點(diǎn)。

var p = document.createElement("p");
document.body.appendChild(p);

如果參數(shù)節(jié)點(diǎn)是文檔中現(xiàn)有的其他節(jié)點(diǎn),appendChild方法會(huì)將其從原來的位置,移動(dòng)到新位置。

hasChildNodes方法返回一個(gè)布爾值,表示當(dāng)前節(jié)點(diǎn)是否有子節(jié)點(diǎn)。

var foo = document.getElementById("foo");

if ( foo.hasChildNodes() ) {
  foo.removeChild( foo.childNodes[0] );
}

上面代碼表示,如果foo節(jié)點(diǎn)有子節(jié)點(diǎn),就移除第一個(gè)子節(jié)點(diǎn)。

(2)hasChildNodes()

hasChildNodes方法結(jié)合firstChild屬性和nextSibling屬性,可以遍歷當(dāng)前節(jié)點(diǎn)的所有后代節(jié)點(diǎn)。

function DOMComb (oParent, oCallback) {
  if (oParent.hasChildNodes()) {
    for (var oNode = oParent.firstChild; oNode; oNode = oNode.nextSibling) {
      DOMComb(oNode, oCallback);
    }
  }
  oCallback.call(oParent);
}

上面代碼的DOMComb函數(shù)的第一個(gè)參數(shù)是某個(gè)指定的節(jié)點(diǎn),第二個(gè)參數(shù)是回調(diào)函數(shù)。這個(gè)回調(diào)函數(shù)會(huì)依次作用于指定節(jié)點(diǎn),以及指定節(jié)點(diǎn)的所有后代節(jié)點(diǎn)。

function printContent () {
  if (this.nodeValue) {
    console.log(this.nodeValue);
  }
}

DOMComb(document.body, printContent);

cloneNode(),insertBefore(),removeChild(),replaceChild()

下面方法與節(jié)點(diǎn)操作有關(guān)。

(1)cloneNode()

cloneNode方法用于克隆一個(gè)節(jié)點(diǎn)。它接受一個(gè)布爾值作為參數(shù),表示是否同時(shí)克隆子節(jié)點(diǎn),默認(rèn)是false,即不克隆子節(jié)點(diǎn)。

var cloneUL = document.querySelector('ul').cloneNode(true);

需要注意的是,克隆一個(gè)節(jié)點(diǎn),會(huì)拷貝該節(jié)點(diǎn)的所有屬性,但是會(huì)喪失addEventListener方法和on-屬性(即node.onclick = fn),添加在這個(gè)節(jié)點(diǎn)上的事件回調(diào)函數(shù)。

克隆一個(gè)節(jié)點(diǎn)之后,DOM樹有可能出現(xiàn)兩個(gè)有相同ID屬性(即id="xxx")的HTML元素,這時(shí)應(yīng)該修改其中一個(gè)HTML元素的ID屬性。

(2)insertBefore()

insertBefore方法用于將某個(gè)節(jié)點(diǎn)插入當(dāng)前節(jié)點(diǎn)的指定位置。它接受兩個(gè)參數(shù),第一個(gè)參數(shù)是所要插入的節(jié)點(diǎn),第二個(gè)參數(shù)是當(dāng)前節(jié)點(diǎn)的一個(gè)子節(jié)點(diǎn),新的節(jié)點(diǎn)將插在這個(gè)節(jié)點(diǎn)的前面。該方法返回被插入的新節(jié)點(diǎn)。

var text1 = document.createTextNode('1');
var li = document.createElement('li');
li.appendChild(text1);

var ul = document.querySelector('ul');
ul.insertBefore(li,ul.firstChild);

上面代碼在ul節(jié)點(diǎn)的最前面,插入一個(gè)新建的li節(jié)點(diǎn)。

如果insertBefore方法的第二個(gè)參數(shù)為null,則新節(jié)點(diǎn)將插在當(dāng)前節(jié)點(diǎn)的最后位置,即變成最后一個(gè)子節(jié)點(diǎn)。

將新節(jié)點(diǎn)插在當(dāng)前節(jié)點(diǎn)的最前面(即變成第一個(gè)子節(jié)點(diǎn)),可以使用當(dāng)前節(jié)點(diǎn)的firstChild屬性。

parentElement.insertBefore(newElement, parentElement.firstChild);

上面代碼中,如果當(dāng)前節(jié)點(diǎn)沒有任何子節(jié)點(diǎn),parentElement.firstChild會(huì)返回null,則新節(jié)點(diǎn)會(huì)插在當(dāng)前節(jié)點(diǎn)的最后,等于是第一個(gè)子節(jié)點(diǎn)。

由于不存在insertAfter方法,如果要插在當(dāng)前節(jié)點(diǎn)的某個(gè)子節(jié)點(diǎn)后面,可以用insertBefore方法結(jié)合nextSibling屬性模擬。

parentDiv.insertBefore(s1, s2.nextSibling);

上面代碼可以將s1節(jié)點(diǎn),插在s2節(jié)點(diǎn)的后面。如果s2是當(dāng)前節(jié)點(diǎn)的最后一個(gè)子節(jié)點(diǎn),則s2.nextSibling返回null,這時(shí)s1節(jié)點(diǎn)會(huì)插在當(dāng)前節(jié)點(diǎn)的最后,變成當(dāng)前節(jié)點(diǎn)的最后一個(gè)子節(jié)點(diǎn),等于緊跟在s2的后面。

(3)removeChild()

removeChild方法接受一個(gè)子節(jié)點(diǎn)作為參數(shù),用于從當(dāng)前節(jié)點(diǎn)移除該節(jié)點(diǎn)。它返回被移除的節(jié)點(diǎn)。

var divA = document.getElementById('A');
divA.parentNode.removeChild(divA);

上面代碼是如何移除一個(gè)指定節(jié)點(diǎn)。

下面是如何移除當(dāng)前節(jié)點(diǎn)的所有子節(jié)點(diǎn)。

var element = document.getElementById("top");
while (element.firstChild) {
  element.removeChild(element.firstChild);
}

被移除的節(jié)點(diǎn)依然存在于內(nèi)存之中,但是不再是DOM的一部分。所以,一個(gè)節(jié)點(diǎn)移除以后,依然可以使用它,比如插入到另一個(gè)節(jié)點(diǎn)。

(4)replaceChild()

replaceChild方法用于將一個(gè)新的節(jié)點(diǎn),替換當(dāng)前節(jié)點(diǎn)的某一個(gè)子節(jié)點(diǎn)。它接受兩個(gè)參數(shù),第一個(gè)參數(shù)是用來替換的新節(jié)點(diǎn),第二個(gè)參數(shù)將要被替換走的子節(jié)點(diǎn)。它返回被替換走的那個(gè)節(jié)點(diǎn)。

replacedNode = parentNode.replaceChild(newChild, oldChild);

下面是一個(gè)例子。

var divA = document.getElementById('A');
var newSpan = document.createElement('span');
newSpan.textContent = 'Hello World!';
divA.parentNode.replaceChild(newSpan,divA);

上面代碼是如何替換指定節(jié)點(diǎn)。

contains(),compareDocumentPosition(),isEqualNode()

下面方法用于節(jié)點(diǎn)的互相比較。

(1)contains()

contains方法接受一個(gè)節(jié)點(diǎn)作為參數(shù),返回一個(gè)布爾值,表示參數(shù)節(jié)點(diǎn)是否為當(dāng)前節(jié)點(diǎn)的后代節(jié)點(diǎn)。

document.body.contains(node)

上面代碼檢查某個(gè)節(jié)點(diǎn),是否包含在當(dāng)前文檔之中。

注意,如果將當(dāng)前節(jié)點(diǎn)傳入contains方法,會(huì)返回true。雖然從意義上說,一個(gè)節(jié)點(diǎn)不應(yīng)該包含自身。

nodeA.contains(nodeA) // true

(2)compareDocumentPosition()

compareDocumentPosition方法的用法,與contains方法完全一致,返回一個(gè)7個(gè)比特位的二進(jìn)制值,表示參數(shù)節(jié)點(diǎn)與當(dāng)前節(jié)點(diǎn)的關(guān)系。

二進(jìn)制值 數(shù)值 含義
000000 0 兩個(gè)節(jié)點(diǎn)相同
000001 1 兩個(gè)節(jié)點(diǎn)不在同一個(gè)文檔(即有一個(gè)節(jié)點(diǎn)不在當(dāng)前文檔)
000010 2 參數(shù)節(jié)點(diǎn)在當(dāng)前節(jié)點(diǎn)的前面
000100 4 參數(shù)節(jié)點(diǎn)在當(dāng)前節(jié)點(diǎn)的后面
001000 8 參數(shù)節(jié)點(diǎn)包含當(dāng)前節(jié)點(diǎn)
010000 16 當(dāng)前節(jié)點(diǎn)包含參數(shù)節(jié)點(diǎn)
100000 32 瀏覽器的私有用途
// HTML代碼為
// <div id="writeroot">
//   <form>
//     <input id="test" />
//   </form>
// </div>

var x = document.getElementById('writeroot');
var y = document.getElementById('test');

x.compareDocumentPosition(y) // 20
y.compareDocumentPosition(x) // 10

上面代碼中,節(jié)點(diǎn)x包含節(jié)點(diǎn)y,而且節(jié)點(diǎn)y在節(jié)點(diǎn)x的后面,所以第一個(gè)compareDocumentPosition方法返回20(010100),第二個(gè)compareDocumentPosition方法返回10(0010010)。

由于compareDocumentPosition返回值的含義,定義在每一個(gè)比特位上,所以如果要檢查某一種特定的含義,就需要使用比特位運(yùn)算符。

var head = document.head;
var body = document.body;
if (head.compareDocumentPosition(body) & 4) {
  console.log("文檔結(jié)構(gòu)正確");
} else {
  console.log("<head> 不能在 <body> 前面");
}

上面代碼中,compareDocumentPosition的返回值與4(又稱掩碼)進(jìn)行與運(yùn)算(&),得到一個(gè)布爾值,表示head是否在body前面。

在這個(gè)方法的基礎(chǔ)上,可以部署一些特定的函數(shù),檢查節(jié)點(diǎn)的位置。

Node.prototype.before = function (arg) {
  return !!(this.compareDocumentPosition(arg) & 2)
}

nodeA.before(nodeB)

上面代碼在Node對(duì)象上部署了一個(gè)before方法,返回一個(gè)布爾值,表示參數(shù)節(jié)點(diǎn)是否在當(dāng)前節(jié)點(diǎn)的前面。

(3)isEqualNode()

isEqualNode方法返回一個(gè)布爾值,用于檢查兩個(gè)節(jié)點(diǎn)是否相等。所謂相等的節(jié)點(diǎn),指的是兩個(gè)節(jié)點(diǎn)的類型相同、屬性相同、子節(jié)點(diǎn)相同。

var targetEl = document.getElementById("targetEl");
var firstDiv = document.getElementsByTagName("div")[0];

targetEl.isEqualNode(firstDiv)

normalize()

normailize方法用于清理當(dāng)前節(jié)點(diǎn)內(nèi)部的所有Text節(jié)點(diǎn)。它會(huì)去除空的文本節(jié)點(diǎn),并且將毗鄰的文本節(jié)點(diǎn)合并成一個(gè)。

var wrapper = document.createElement("div");

wrapper.appendChild(document.createTextNode("Part 1 "));
wrapper.appendChild(document.createTextNode("Part 2 "));

wrapper.childNodes.length // 2

wrapper.normalize();

wrapper.childNodes.length // 1

上面代碼使用normalize方法之前,wrapper節(jié)點(diǎn)有兩個(gè)Text子節(jié)點(diǎn)。使用normalize方法之后,兩個(gè)Text子節(jié)點(diǎn)被合并成一個(gè)。

該方法是Text.splitText的逆方法,可以查看《Text節(jié)點(diǎn)》章節(jié),了解更多內(nèi)容。

NodeList接口,HTMLCollection接口

節(jié)點(diǎn)對(duì)象都是單個(gè)節(jié)點(diǎn),但是有時(shí)會(huì)需要一種數(shù)據(jù)結(jié)構(gòu),能夠容納多個(gè)節(jié)點(diǎn)。DOM提供兩種接口,用于部署這種節(jié)點(diǎn)的集合:NodeList接口和HTMLCollection接口。

NodeList接口

有些屬性和方法返回的是一組節(jié)點(diǎn),比如Node.childNodes、document.querySelectorAll()。它們返回的都是一個(gè)部署了NodeList接口的對(duì)象。

NodeList接口有時(shí)返回一個(gè)動(dòng)態(tài)集合,有時(shí)返回一個(gè)靜態(tài)集合。所謂動(dòng)態(tài)集合就是一個(gè)活的集合,DOM樹刪除或新增一個(gè)相關(guān)節(jié)點(diǎn),都會(huì)立刻反映在NodeList接口之中。Node.childNodes返回的,就是一個(gè)動(dòng)態(tài)集合。

var parent = document.getElementById('parent');
parent.childNodes.length // 2
parent.appendChild(document.createElement('div'));
parent.childNodes.length // 3

上面代碼中,parent.childNodes返回的是一個(gè)部署了NodeList接口的對(duì)象。當(dāng)parent節(jié)點(diǎn)新增一個(gè)子節(jié)點(diǎn)以后,該對(duì)象的成員個(gè)數(shù)就增加了1。

document.querySelectorAll方法返回的是一個(gè)靜態(tài),DOM內(nèi)部的變化,并不會(huì)實(shí)時(shí)反映在該方法的返回結(jié)果之中。

NodeList接口提供length屬性和數(shù)字索引,因此可以像數(shù)組那樣,使用數(shù)字索引取出每個(gè)節(jié)點(diǎn),但是它本身并不是數(shù)組,不能使用pop或push之類數(shù)組特有的方法。

// 數(shù)組的繼承鏈
myArray --> Array.prototype --> Object.prototype --> null

// NodeList的繼承鏈
myNodeList --> NodeList.prototype --> Object.prototype --> null

從上面的繼承鏈可以看到,NodeList接口對(duì)象并不繼承Array.prototype,因此不具有數(shù)組接口提供的方法。如果要在NodeList接口使用數(shù)組方法,可以將NodeList接口對(duì)象轉(zhuǎn)為真正的數(shù)組。

var div_list = document.querySelectorAll('div');
var div_array = Array.prototype.slice.call(div_list);

也可以通過下面的方法調(diào)用。

var forEach = Array.prototype.forEach;

forEach.call(element.childNodes, function(child){
  child.parentNode.style.color = '#0F0';
});

上面代碼讓數(shù)組的forEach方法在NodeList接口對(duì)象上調(diào)用。

不過,遍歷NodeList接口對(duì)象的首選方法,還是使用for循環(huán)。

for (var i = 0; i < myNodeList.length; ++i) {
  var item = myNodeList[i];
}

不要使用for...in循環(huán)去遍歷NodeList接口對(duì)象,因?yàn)閒or...in循環(huán)會(huì)將非數(shù)字索引的length屬性和下面要講到的item方法,也遍歷進(jìn)去,而且不保證各個(gè)成員遍歷的順序。

ES6新增的for...of循環(huán),也可以正確遍歷NodeList接口對(duì)象。

var list = document.querySelectorAll( 'input[type=checkbox]' );
for (var item of list) {
  item.checked = true;
}

NodeList接口提供item方法,接受一個(gè)數(shù)字索引作為參數(shù),返回該索引對(duì)應(yīng)的成員。如果取不到成員,或者索引不合法,則返回null。

nodeItem = nodeList.item(index)

// 實(shí)例
var divs = document.getElementsByTagName("div");
var secondDiv = divs.item(1);

上面代碼中,由于數(shù)字索引從零開始計(jì)數(shù),所以取出第二個(gè)成員,要使用數(shù)字索引1。

所有類似數(shù)組的對(duì)象,都可以使用方括號(hào)運(yùn)算符取出成員,所以一般情況下,都是使用下面的寫法,而不使用item方法。

nodeItem = nodeList[index]

HTMLCollection接口

HTMLCollection接口與NodeList接口類似,也是節(jié)點(diǎn)的集合,但是集合成員都是Element節(jié)點(diǎn)。該接口都是動(dòng)態(tài)集合,節(jié)點(diǎn)的變化會(huì)實(shí)時(shí)反映在集合中。document.links、docuement.forms、document.images等屬性,返回的都是HTMLCollection接口對(duì)象。

部署了該接口的對(duì)象,具有l(wèi)ength屬性和數(shù)字索引,因此是一個(gè)類似于數(shù)組的對(duì)象。

item方法根據(jù)成員的位置參數(shù)(從0開始),返回該成員。如果取不到成員或數(shù)字索引不合法,則返回null。

var c = document.images;
var img1 = c.item(10);

// 等價(jià)于下面的寫法
var img1 = c[1];

namedItem方法根據(jù)成員的ID屬性或name屬性,返回該成員。如果沒有對(duì)應(yīng)的成員,則返回null。

// HTML代碼為
// <form id="myForm"></form>
var elem = document.forms.namedItem("myForm");
// 等價(jià)于下面的寫法
var elem = document.forms["myForm"];

由于item方法和namedItem方法,都可以用方括號(hào)運(yùn)算符代替,所以建議一律使用方括號(hào)運(yùn)算符。

ParentNode接口,ChildNode接口

不同的節(jié)點(diǎn)除了繼承Node接口以外,還會(huì)繼承其他接口。ParentNode接口用于獲取當(dāng)前節(jié)點(diǎn)的Element子節(jié)點(diǎn),ChildNode接口用于處理當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn)(包含但不限于Element子節(jié)點(diǎn))。

ParentNode接口

ParentNode接口用于獲取Element子節(jié)點(diǎn)。Element節(jié)點(diǎn)、Document節(jié)點(diǎn)和DocumentFragment節(jié)點(diǎn),部署了ParentNode接口。凡是這三類節(jié)點(diǎn),都具有以下四個(gè)屬性,用于獲取Element子節(jié)點(diǎn)。

(1)children

children屬性返回一個(gè)動(dòng)態(tài)的HTMLCollection集合,由當(dāng)前節(jié)點(diǎn)的所有Element子節(jié)點(diǎn)組成。

下面代碼遍歷指定節(jié)點(diǎn)的所有Element子節(jié)點(diǎn)。

if (el.children.length) {
  for (var i = 0; i < el.children.length; i++) {
    // ...
  }
}

(2)firstElementChild

firstChild屬性返回當(dāng)前節(jié)點(diǎn)的第一個(gè)Element子節(jié)點(diǎn),如果不存在任何Element子節(jié)點(diǎn),則返回null。

document.firstElementChild.nodeName
// "HTML"

上面代碼中,document節(jié)點(diǎn)的第一個(gè)Element子節(jié)點(diǎn)是。

(3)lastElementChild

lastElementChild屬性返回當(dāng)前節(jié)點(diǎn)的最后一個(gè)Element子節(jié)點(diǎn),如果不存在任何Element子節(jié)點(diǎn),則返回null。

document.lastElementChild.nodeName
// "HTML"

上面代碼中,document節(jié)點(diǎn)的最后一個(gè)Element子節(jié)點(diǎn)是。

(4)childElementCount

childElementCount屬性返回當(dāng)前節(jié)點(diǎn)的所有Element子節(jié)點(diǎn)的數(shù)目。

ChildNode接口

ChildNode接口用于處理子節(jié)點(diǎn)(包含但不限于Element子節(jié)點(diǎn))。Element節(jié)點(diǎn)、DocumentType節(jié)點(diǎn)和CharacterData接口,部署了ChildNode接口。凡是這三類節(jié)點(diǎn)(接口),都可以使用下面四個(gè)方法。但是現(xiàn)實(shí)的情況是,除了第一個(gè)remove方法,目前沒有瀏覽器支持后面三個(gè)方法。

(1)remove()

remove方法用于移除當(dāng)前節(jié)點(diǎn)。

el.remove()

上面方法在DOM中移除了el節(jié)點(diǎn)。注意,調(diào)用這個(gè)方法的節(jié)點(diǎn),是被移除的節(jié)點(diǎn)本身,而不是它的父節(jié)點(diǎn)。

(2)before()

before方法用于在當(dāng)前節(jié)點(diǎn)的前面,插入一個(gè)同級(jí)節(jié)點(diǎn)。如果參數(shù)是節(jié)點(diǎn)對(duì)象,插入DOM的就是該節(jié)點(diǎn)對(duì)象;如果參數(shù)是文本,插入DOM的就是參數(shù)對(duì)應(yīng)的文本節(jié)點(diǎn)。

(3)after()

after方法用于在當(dāng)前節(jié)點(diǎn)的后面,插入一個(gè)同級(jí)節(jié)點(diǎn)。如果參數(shù)是節(jié)點(diǎn)對(duì)象,插入DOM的就是該節(jié)點(diǎn)對(duì)象;如果參數(shù)是文本,插入DOM的就是參數(shù)對(duì)應(yīng)的文本節(jié)點(diǎn)。

(4)replaceWith()

replaceWith方法使用參數(shù)指定的節(jié)點(diǎn),替換當(dāng)前節(jié)點(diǎn)。如果參數(shù)是節(jié)點(diǎn)對(duì)象,替換當(dāng)前節(jié)點(diǎn)的就是該節(jié)點(diǎn)對(duì)象;如果參數(shù)是文本,替換當(dāng)前節(jié)點(diǎn)的就是參數(shù)對(duì)應(yīng)的文本節(jié)點(diǎn)。

html元素

html元素是網(wǎng)頁的根元素,document.documentElement就指向這個(gè)元素。

(1)clientWidth屬性,clientHeight屬性

這兩個(gè)屬性返回視口(viewport)的大小,單位為像素。所謂“視口”,是指用戶當(dāng)前能夠看見的那部分網(wǎng)頁的大小

document.documentElement.clientWidth和document.documentElement.clientHeight,基本上與window.innerWidth和window.innerHeight同義。只有一個(gè)區(qū)別,前者不將滾動(dòng)條計(jì)算在內(nèi)(很顯然,滾動(dòng)條和工具欄會(huì)減小視口大小),而后者包括了滾動(dòng)條的高度和寬度。

(2)offsetWidth屬性,offsetHeight屬性

這兩個(gè)屬性返回html元素的寬度和高度,即網(wǎng)頁的總寬度和總高度。

dataset屬性

dataset屬性用于操作HTML標(biāo)簽元素的data-*屬性。目前,F(xiàn)irefox、Chrome、Opera、Safari瀏覽器支持該API。

假設(shè)有如下的網(wǎng)頁代碼。

<div id="myDiv" data-id="myId"></div>

以data-id屬性為例,要讀取這個(gè)值,可以用dataset.id。

var id = document.getElementById("myDiv").dataset.id;

要設(shè)置data-id屬性,可以直接對(duì)dataset.id賦值。這時(shí),如果data-id屬性不存在,將會(huì)被創(chuàng)造出來。

document.getElementById("myDiv").dataset.id = "hello";

刪除一個(gè)data-*屬性,可以直接使用delete命令。

delete document.getElementById("myDiv").dataset.id

IE 9不支持dataset屬性,可以用 getAttribute('data-foo')、removeAttribute('data-foo')、setAttribute('data-foo')、hasAttribute('data-foo') 代替。

需要注意的是,dataset屬性使用駱駝拼寫法表示屬性名,這意味著data-hello-world會(huì)用dataset.helloWorld表示。而如果此時(shí)存在一個(gè)data-helloWorld屬性,該屬性將無法讀取,也就是說,data屬性本身只能使用連詞號(hào),不能使用駱駝拼寫法。

tabindex屬性

tabindex屬性用來指定,當(dāng)前HTML元素節(jié)點(diǎn)是否被tab鍵遍歷,以及遍歷的優(yōu)先級(jí)。

var b1 = document.getElementById("button1");

b1.tabIndex = 1;

如果 tabindex = -1 ,tab鍵跳過當(dāng)前元素。

如果 tabindex = 0 ,表示tab鍵將遍歷當(dāng)前元素。如果一個(gè)元素沒有設(shè)置tabindex,默認(rèn)值就是0。

如果 tabindex 大于0,表示tab鍵優(yōu)先遍歷。值越大,就表示優(yōu)先級(jí)越大。

頁面位置相關(guān)屬性

(1)offsetParent屬性、offsetTop屬性和offsetLeft屬性

這三個(gè)屬性提供Element對(duì)象在頁面上的位置。

  • offsetParent:當(dāng)前HTML元素的最靠近的、并且CSS的position屬性不等于static的父元素。
  • offsetTop:當(dāng)前HTML元素左上角相對(duì)于offsetParent的垂直位移。
  • offsetLeft:當(dāng)前HTML元素左上角相對(duì)于offsetParent的水平位移。

如果Element對(duì)象的父對(duì)象都沒有將position屬性設(shè)置為非static的值(比如absolute或relative),則offsetParent屬性指向body元素。另外,計(jì)算offsetTop和offsetLeft的時(shí)候,是從邊框的左上角開始計(jì)算,即Element對(duì)象的border寬度不計(jì)入offsetTop和offsetLeft。

style屬性

style屬性用來讀寫頁面元素的行內(nèi)CSS屬性,詳見本章《CSS操作》一節(jié)。

Element對(duì)象的方法

(1)選擇子元素的方法

Element對(duì)象也部署了document對(duì)象的4個(gè)選擇子元素的方法,而且用法完全一樣。

  • querySelector方法
  • querySelectorAll方法
  • getElementsByTagName方法
  • getElementsByClassName方法

上面四個(gè)方法只用于選擇Element對(duì)象的子節(jié)點(diǎn)。因此,可以采用鏈?zhǔn)綄懛▉磉x擇子節(jié)點(diǎn)。

document.getElementById('header').getElementsByClassName('a')

各大瀏覽器對(duì)這四個(gè)方法都支持良好,IE的情況如下:IE 6開始支持getElementsByTagName,IE 8開始支持querySelector和querySelectorAll,IE 9開始支持getElementsByClassName。

(2)elementFromPoint方法

該方法用于選擇在指定坐標(biāo)的最上層的Element對(duì)象。

document.elementFromPoint(50,50)

上面代碼了選中在(50,50)這個(gè)坐標(biāo)的最上層的那個(gè)HTML元素。

(3)HTML元素的屬性相關(guān)方法

  • hasAttribute():返回一個(gè)布爾值,表示Element對(duì)象是否有該屬性。
  • getAttribute()
  • setAttribute()
  • removeAttribute()

(4)matchesSelector方法

該方法返回一個(gè)布爾值,表示Element對(duì)象是否符合某個(gè)CSS選擇器。

document.querySelector('li').matchesSelector('li:first-child')

這個(gè)方法需要加上瀏覽器前綴,需要寫成mozMatchesSelector()、webkitMatchesSelector()、oMatchesSelector()、msMatchesSelector()。

(5)focus方法

focus方法用于將當(dāng)前頁面的焦點(diǎn),轉(zhuǎn)移到指定元素上。

document.getElementById('my-span').focus();

table元素

表格有一些特殊的DOM操作方法。

  • insertRow():在指定位置插入一個(gè)新行(tr)。
  • deleteRow():在指定位置刪除一行(tr)。
  • insertCell():在指定位置插入一個(gè)單元格(td)。
  • deleteCell():在指定位置刪除一個(gè)單元格(td)。
  • createCaption():插入標(biāo)題。
  • deleteCaption():刪除標(biāo)題。
  • createTHead():插入表頭。
  • deleteTHead():刪除表頭。

下面是使用JavaScript生成表格的一個(gè)例子。

var table = document.createElement('table');
var tbody = document.createElement('tbody');
table.appendChild(tbody);

for (var i = 0; i <= 9; i++) {
  var rowcount = i + 1;
  tbody.insertRow(i);
  tbody.rows[i].insertCell(0);
  tbody.rows[i].insertCell(1);
  tbody.rows[i].insertCell(2);
  tbody.rows[i].cells[0].appendChild(document.createTextNode('Row ' + rowcount + ', Cell 1'));
  tbody.rows[i].cells[1].appendChild(document.createTextNode('Row ' + rowcount + ', Cell 2'));
  tbody.rows[i].cells[2].appendChild(document.createTextNode('Row ' + rowcount + ', Cell 3'));
}

table.createCaption();
table.caption.appendChild(document.createTextNode('A DOM-Generated Table'));

document.body.appendChild(table);

這些代碼相當(dāng)易讀,其中需要注意的就是insertRow和insertCell方法,接受一個(gè)表示位置的參數(shù)(從0開始的整數(shù))。

table元素有以下屬性:

  • caption:標(biāo)題。
  • tHead:表頭。
  • tFoot:表尾。
  • rows:行元素對(duì)象,該屬性只讀。
  • rows.cells:每一行的單元格對(duì)象,該屬性只讀。
  • tBodies:表體,該屬性只讀。

參考鏈接

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)