摘要:年月級(jí)規(guī)范成為的推薦標(biāo)準(zhǔn),為基本的文檔結(jié)構(gòu)及查詢提供了接口。這意味著中的對(duì)象與原生對(duì)象的行為或活動(dòng)特點(diǎn)并不一致。結(jié)果第一條注釋就會(huì)成為中的第一個(gè)子節(jié)點(diǎn)。由于跨域安全限制,來自不同子域的頁(yè)面無法通過通信。
DOM(文檔對(duì)象模型)是針對(duì)HTML和XML文檔的一個(gè)API(應(yīng)用程序編程接口)。DOM描繪了一個(gè)層次化的節(jié)點(diǎn)樹,允許開發(fā)人員添加、移除和修改頁(yè)面的某一部分。DOM脫胎于Netscape及微軟公司創(chuàng)始的DHTML(動(dòng)態(tài)HTML),但現(xiàn)在它已經(jīng)成為表現(xiàn)和操作頁(yè)面標(biāo)記的真正跨平臺(tái)、語言中立方式。
1998年10月DOM 1 級(jí)規(guī)范成為W3C的推薦標(biāo)準(zhǔn),為基本的文檔結(jié)構(gòu)及查詢提供了接口。本章主要討論與瀏覽器中的HTML頁(yè)面相關(guān)的DOM1級(jí)的特性和應(yīng)用,以及JavaScript對(duì)DOM1級(jí)的視線。
IE中的所有DOM對(duì)象都是以COM對(duì)象的形式實(shí)現(xiàn)的。這意味著IE中的DOM對(duì)象與原生JavaScript對(duì)象的行為或活動(dòng)特點(diǎn)并不一致。本章將較多的談及這些差異。
節(jié)點(diǎn)層次Sample Page Hello World!
可以將這個(gè)簡(jiǎn)單的HTML文檔視為一個(gè)層次結(jié)構(gòu),如圖10-1所示。
文檔節(jié)點(diǎn)是每個(gè)文檔的根節(jié)點(diǎn)。在這個(gè)例子中,文檔節(jié)點(diǎn)只有一個(gè)子節(jié)點(diǎn),既元素,我們稱之為文檔元素。
文檔元素是文檔的最外層元素,文檔中的其他所有元素都包含在文檔元素中。每個(gè)文檔只能有一個(gè)文檔元素。在HTML頁(yè)面中,文檔元素始終都是元素。在XML中,沒有預(yù)定義的元素,因此任何元素都可能成為文檔元素。
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)的類型。及誒單類型由在Node類型中定義的下列12個(gè)數(shù)值常量來表示,任何節(jié)點(diǎn)類型必居其一(編號(hào)為節(jié)點(diǎn)類型常量存儲(chǔ)的數(shù)值):
Node.ELEMENT_NODE
Node.ATTRIBUTE_NODE
Node.TEXT_NODE
Node.CDATA_SECTION_NODE
Node.ENTITY_REFERENCE_NODE
Node.ENTITY_NODE
Node.PROCESSING_INSTRUCTION_NODE
Node.COMMENT_NODE
Node.DOCUMENT_NODE
Node.DOCUMENT_TYPE_NODE
Node.DOCUMENT_FRAGMENT_NODE
Node.NOTATION_NODE
// 通過比較上面的常量,很容易的確定節(jié)點(diǎn)類型 // 在IE中無效 if (someNode.nodeType == Node.ELEMENT_NODE) { console.log("Node is an element"); } // 由于IE沒有公開 Node 類型的構(gòu)造函數(shù) // 最好還是將 nodeType 屬性與數(shù)字比較 if (someNode.nodeType == 1) { console.log("Node is an element"); }
并不是所有節(jié)點(diǎn)類型都受到Web瀏覽器的支持。開發(fā)人員最常用的就是元素和文本節(jié)點(diǎn)。
nodeName 和 nodeValue 屬性了解節(jié)點(diǎn)的具體信息,可以使用nodeName 和 nodeValue 兩個(gè)屬性。這兩個(gè)屬性的值完全取決于節(jié)點(diǎn)類型。在使用這兩個(gè)值以前,最好用上述的代碼檢查節(jié)點(diǎn)的類型。
if (someNode.nodeType == 1) { value = someNode.nodeName; // nodeName的值是元素的標(biāo)簽名 }節(jié)點(diǎn)關(guān)系
每個(gè)節(jié)點(diǎn)都有一個(gè)childNodes屬性,其中保存著一個(gè)NodeList對(duì)象。注意,可以通過方括號(hào)語法來訪問NodeList的值,而且也有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是有生命、有呼吸的對(duì)象,而不是我們第一次訪它的瞬間拍攝下來的一張快照。
// 方括號(hào)和 item() 語法結(jié)果是相同的 var firstChild = someNode.childNodes[0]; var secondChild = someNode.childNodes.item(1); var count = comeNode.childNodes.length; // 雖然不是Array的實(shí)例,但我們可以將它轉(zhuǎn)換成數(shù)組 // 在IE8及之前的版本中無效 var arrayOfNodes = Array.prototype.slice.call(someNode.childNodes, 0);
// 由于IE8及更早版本將 NodeList 實(shí)現(xiàn)為一個(gè) COM 對(duì)象 // 必須手動(dòng)枚舉所有成員,才能轉(zhuǎn)換成數(shù)組 function convertToArray(nodes) { var array = null; try { array = Array.prototype.slice.call(nodes, 0); // 針對(duì)非IE瀏覽器 } catch (ex) { array = new Array(); for (var i=0, len=nodes.length; i < len; i++) { array.push(nodes[i]); } } return array; }
每個(gè)節(jié)點(diǎn)都有一個(gè)parentNode屬性,指向文檔中的父節(jié)點(diǎn)。
包含在childNodes中的所有節(jié)點(diǎn)都具有相同的父節(jié)點(diǎn),而相互之間是同胞節(jié)點(diǎn)。
通過每個(gè)節(jié)點(diǎn)的previousSibling 和 nextSibling 屬性可以訪問同一列表中的其他節(jié)點(diǎn)。列表第一個(gè)節(jié)點(diǎn)previousSibling為null,列表最后一個(gè)nextSibling為null,當(dāng)然如果列表只有一個(gè)節(jié)點(diǎn),那么兩個(gè)都是null。
父節(jié)點(diǎn)的firstChild和lastChild屬性分別指向第一個(gè)和最后一個(gè)。如果列表沒有節(jié)點(diǎn),那么兩個(gè)屬性都是null。
hasChildNodes()也是一個(gè)非常有用的方法,當(dāng)查詢節(jié)點(diǎn)存在子節(jié)點(diǎn)時(shí)返回true,不存在返回false。這是比查詢childNodes.length更簡(jiǎn)單的方法。
所有節(jié)點(diǎn)都有的最后一個(gè)屬性是ownerDocument,該屬性指向表示整個(gè)文檔的文檔節(jié)點(diǎn)。這種關(guān)系表示的是任何節(jié)點(diǎn)都屬于它所在的文檔,任何節(jié)點(diǎn)都不能同時(shí)存在兩個(gè)或更多個(gè)文檔中。通過這個(gè)屬性,我們可以不必在節(jié)點(diǎn)層次中通過層層回溯達(dá)到頂端,而是可以直接訪問文檔節(jié)點(diǎn)。
操作節(jié)點(diǎn)因?yàn)殛P(guān)系指針都是只讀的,所以DOM提供了一些操作節(jié)點(diǎn)的方法 。
最常用的方法是appendChild(),用于向childNodes列表的末尾添加一個(gè)節(jié)點(diǎn),執(zhí)行后,方法返回新增的節(jié)點(diǎn)。
var returnedNode = someNode.appendChild(newNode); console.log(returnedNode == newNode); // true console.log(someNode.lastChild ==newNode); // 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) var returnedNode = someNode.insertBefore(newNode, null); // 插入后成為第一個(gè)子節(jié)點(diǎn) var returnedNode = someNode.insertBefore(newNode, someNode.firstChild); // 插入后在最后一個(gè)子節(jié)點(diǎn)前面 var returnedNode = someNode.insertBefore(newNode, someNode.lastChild);
replaceChild() 替換節(jié)點(diǎn)。同樣接收兩個(gè)參數(shù),插入的節(jié)點(diǎn)和參照節(jié)點(diǎn)。插入新的節(jié)點(diǎn)并將參照節(jié)點(diǎn)從文檔樹中移除,新的節(jié)點(diǎn)會(huì)從被替換的節(jié)點(diǎn)復(fù)制所有關(guān)系指針。盡管從技術(shù)上講,被替換的節(jié)點(diǎn)仍然在文檔中,但它在文檔中的位置已經(jīng)不存在了。
removeChild() 移除節(jié)點(diǎn)。被移除的節(jié)點(diǎn)仍然在文檔中,但它在文檔中的位置已經(jīng)不存在了。
以上四個(gè)方法必須先取得操作節(jié)點(diǎn)的父節(jié)點(diǎn)(代碼示例中是someNode)。在不存在子節(jié)點(diǎn)的節(jié)點(diǎn)上調(diào)用以上方法,會(huì)導(dǎo)致錯(cuò)誤。
其他方法還有兩個(gè)方法是所有節(jié)點(diǎn)都有的。
cloneNode() 用于創(chuàng)建調(diào)用這個(gè)方法的節(jié)點(diǎn)的一個(gè)完全相同的副本。接收一個(gè)布爾值參數(shù),表示是否執(zhí)行深復(fù)制。
傳入true。執(zhí)行深復(fù)制,復(fù)制節(jié)點(diǎn)及其整個(gè)子節(jié)點(diǎn)樹
傳入false。執(zhí)行淺復(fù)制,即只復(fù)制節(jié)點(diǎn)本身。
復(fù)制返回的節(jié)點(diǎn)副本屬于文檔所有,但并沒有為它制定父節(jié)點(diǎn)。因此這個(gè)節(jié)點(diǎn)副本就成為了一個(gè)“孤兒”,除非通過 appendChild() insertBefore() replaceChild() 將它添加到文檔中。
IE8及之前的版本不會(huì)為包含空白符的文字創(chuàng)建節(jié)點(diǎn)(TEXT)
clone() 方法不會(huì)復(fù)制添加到DOM節(jié)點(diǎn)中的JavaScript屬性,例如時(shí)間處理程序。這個(gè)方法只復(fù)制特性、(在明確指定的情況下也復(fù)制)子節(jié)點(diǎn),其他一切都不會(huì)復(fù)制。
IE 會(huì)復(fù)制事件處理程序,所以我們建議在復(fù)制之前最好先移出事件處理程序。
var myList = document.getElementById("ul"); var deepList = myList.cloneNode(true); // [text, li, text, li, text, li, text] console.log(deepList.childNodes); // 3 (IE < 9) 或 7 (其他瀏覽器) // IE8及之前的版本不會(huì)為包含空白符的文字創(chuàng)建節(jié)點(diǎn)(TEXT) console.log(deepList.childNodes.length); var shallowList = myList.cloneNode(false); console.log(shallowList.childNodes.length); // 0
normalize() 方法唯一的作用就是處理文檔樹中的文本節(jié)點(diǎn)。由于解析器的實(shí)現(xiàn)或DOM操作等原因,可能會(huì)出現(xiàn)文本節(jié)點(diǎn)不包含文本,或者連接出現(xiàn)兩個(gè)節(jié)點(diǎn)的情況。當(dāng)在某個(gè)節(jié)點(diǎn)上調(diào)用這個(gè)方法時(shí),就會(huì)在該節(jié)點(diǎn)的后代節(jié)點(diǎn)中查找上述兩種情況。
如果找到了空文本節(jié)點(diǎn),則刪除它
如果找到相鄰的文本節(jié)點(diǎn),則將它們合并為一個(gè)文本節(jié)點(diǎn)
本章后面還將進(jìn)一步討論方法
var html = document.documentElement; // 取得對(duì)的引用 console.log(html == document.childNodes[0]); // true console.log(html == document.firstchild) // true
所有瀏覽器都支持document.documentElement 和 document.boyd 屬性
Document另一個(gè)可能的子節(jié)點(diǎn)是DocumentType。通常將標(biāo)簽看成一個(gè)與文檔其他部分不同的實(shí)體,可以通過doctype屬性(在瀏覽器中是document.doctype)來訪問信息。
瀏覽器對(duì)document.doctype的支持差別很大,所以這個(gè)屬性的用途很有限:
IE8及之前版本,如果存在文檔類型聲明,會(huì)將其錯(cuò)誤的解釋為一個(gè)注釋并把它當(dāng)做Comment節(jié)點(diǎn);而document.doctype的值始終為null
IE9+,如果存在文檔類型聲明,則將其作為文檔的第一個(gè)子節(jié)點(diǎn);document.doctype是一個(gè)DocumentType節(jié)點(diǎn),也可以通過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中。
從技術(shù)上說,出現(xiàn)在元素外部的注釋應(yīng)該是算是文檔的子節(jié)點(diǎn)。然而,不同的瀏覽器在是否解析這些注釋以及能否正確處理他們等方面,也存在很大差異。
看起來這個(gè)頁(yè)面應(yīng)該有3個(gè)子節(jié)點(diǎn):注釋、元素、注釋。從邏輯上講,我們會(huì)認(rèn)為document.childNodes中應(yīng)該包含與這3個(gè)節(jié)點(diǎn)對(duì)應(yīng)的3項(xiàng)。但是實(shí)際上,瀏覽器存在以下差異:
IE8及之前版本、Safari3.1及更高版本、Opera和Chrome 只為第一條注釋創(chuàng)建節(jié)點(diǎn),不為第二條注釋創(chuàng)建節(jié)點(diǎn)。結(jié)果第一條注釋就會(huì)成為document.childNodes中的第一個(gè)子節(jié)點(diǎn)。
IE9+,將會(huì)將兩條都創(chuàng)建節(jié)點(diǎn)。
Firefox 和 Safari3.1之前的版本會(huì)完全忽略這兩條注釋。
多數(shù)情況下,我們都用不著在document對(duì)象上調(diào)用appendChild() removeChild() replaceChild() 方法,因?yàn)槲臋n類型(如果存在的話)是只讀的,而且它只能有一個(gè)元素子節(jié)點(diǎn)(該節(jié)點(diǎn)通常早就已經(jīng)存在了)。
文檔信息作為HMLTDocument的一個(gè)實(shí)例,document對(duì)象還有一些標(biāo)準(zhǔn)的Document對(duì)象所沒有的屬性。
title 包含著
// 取得文檔標(biāo)題 var originalTitle = document.title; // 設(shè)置文檔標(biāo)題 document.title = "New page title";
下面三個(gè)屬性與網(wǎng)頁(yè)的請(qǐng)求有關(guān),所有這些信息都存在于請(qǐng)求的HTTP頭部,只不過是通過這些屬性讓我們能夠在JavaScript中訪問它們而已:
URL屬性中包含頁(yè)面完整的URL(地址欄中的URL)
domain屬性中值包含頁(yè)面的域名
referrer屬性中可能會(huì)包含空字符串
URL與domain屬性是相互關(guān)聯(lián)的。例如document.URL等于"http://www.wrox.com/WileyCDA/",那么document.domain就等于"www.wrox.com"。
3個(gè)屬性中只有domain可以設(shè)置,但有安全方面的限制。如果URL中包含一個(gè)子域名,例如"p2p.wrox.com",那么就只能講domain設(shè)置為"wrox.com"(URL中包含"www",如"www.wrox.com"時(shí),也是如此)。
當(dāng)頁(yè)面中包含來自其他子域的框架或內(nèi)嵌框架時(shí),能夠設(shè)置document.domain就非常方便了。由于跨域安全限制,來自不同子域的頁(yè)面無法通過JavaScript通信。而通過將每個(gè)頁(yè)面的document.domain設(shè)置為相同的值,這些頁(yè)面就可以互相訪問對(duì)方包含的JavaScript對(duì)象了。
// 取得完整的URL var url = document.URL; // 取得域名 var domain = document.domain; // 取得來源 頁(yè)面的URL var referrer = document.referrer;
瀏覽器對(duì)domain屬性還有一個(gè)限制,即如果域名一開始是“松散的”(loose),那么就不能將它再設(shè)置為“緊繃的”(tight)。
// 假設(shè)頁(yè)面來自于 p2p.wrox.com域 document.domain = "wrox.com"; // 松散的(成功) document.domain = "p2p.wrox.com"; // 緊繃的(出錯(cuò))查找元素
getElementById() 接收一個(gè)參數(shù):要取得的元素的ID。找到相應(yīng)的元素則返回該元素,否則返回null。
IE8及較低版本不區(qū)分ID大小寫
如果頁(yè)面多個(gè)元素的ID相同,只會(huì)返回第一個(gè)匹配的元素。
IE7及更早的版本添加了一個(gè)怪癖:name特性與給定ID匹配的表單元素也會(huì)被該方法返回。
A div
getElementsByTagName() 接收一個(gè)參數(shù):要取得的元素的標(biāo)簽名,而返回的是包含零或多個(gè)元素的NodeList??梢允褂梅嚼ㄌ?hào)語法或item()方法來訪問對(duì)象中的項(xiàng)。
namedItem() 使用這個(gè)方法可以通過元素的name特性取得集合中的項(xiàng)?;蚍嚼ㄌ?hào)語法能達(dá)到同樣的效果
要取得文檔中的所有元素,可以向getElementsByTagName()中傳入"*"。在JavaScript及CSS中,星號(hào)通常表示全部。
雖然標(biāo)準(zhǔn)規(guī)定標(biāo)簽名需要區(qū)分大小寫,但為了最大限度的與既有HTML頁(yè)面兼容,傳給getElementsByTagName()的標(biāo)簽名是不需要區(qū)分大小寫的。但對(duì)于XML頁(yè)面而言(包括XHTML),getElementsByTagName()方法就會(huì)區(qū)分大小寫。
getElementByName() 是只有HTMLDocument類型才有的方法,返回帶有給定name屬性的所有元素。最常使用的情況是取得單選按鈕;為了確保發(fā)送給瀏覽器的值正確無誤,所有單選按鈕必須具有相同的name特性
上述例子使用getElementsByName()方法可以返回三個(gè)input元素。但是對(duì)于這里的單選按鈕來說namedItem()方法只會(huì)取得第一項(xiàng)(因?yàn)槊恳豁?xiàng)的name特性都相同)。
特殊集合document.anchors 包含文檔中所有帶name特性的元素
document.applets 包含文檔中所有的元素,與document.getElementsByTagName("form")得到的結(jié)果相同
document.images 包含文檔中所有的元素,與document.getElementsByTagName("img")得到的結(jié)果相同
document.links 包含文檔中所有帶 href 特性的元素
DOM一致性檢測(cè)由于DOM分為多個(gè)級(jí)別,也包含多個(gè)部分,因此檢測(cè)瀏覽器實(shí)現(xiàn)了DOM的哪些部分就十分必要。document.implementation屬性就是為此提供的,與瀏覽器對(duì)DOM的實(shí)現(xiàn)直接對(duì)應(yīng)。
DOM1級(jí)別只為document.implementation規(guī)定了一個(gè)方法,即hasFeature()。接收兩個(gè)參數(shù):要檢測(cè)的DOM功能的名稱及版本號(hào)。如果支持返回true
var hasXmlDom = docuemnt.implementation.hasFeature("XML", "1.0");
下表列出了可以檢測(cè)的不同值得版本號(hào)
功能 | 版本號(hào) | 說明 |
---|---|---|
Core | 1.0、2.0、3.0 | 基本的DOM,用于描述表現(xiàn)文檔的節(jié)點(diǎn)樹 |
XML | 1.0、2.0、3.0 | Core的XML拓展,添加了對(duì)CDATA、處理指令及實(shí)體的支持 |
HTML | 1.0、2.0 | XML的HTML拓展,添加了對(duì)HTML特有元素及實(shí)體的支持 |
Views | 2.0 | 基于某些樣式完成文檔的格式化 |
StyleSheets | 2.0 | 將樣式表關(guān)聯(lián)到文檔 |
CSS | 2.0 | 對(duì)層疊樣式表1級(jí)的支持 |
CSS2 | 2.0 | 對(duì)層疊樣式表2級(jí)的支持 |
Events | 2.0, 3.0 | 常規(guī)的DOM事件 |
UIEvents | 2.0, 3.0 | 用戶界面事件 |
MouseEvents | 2.0, 3.0 | 由鼠標(biāo)引發(fā)的事件(click、mouseover等) |
MutationEvents | 2.0, 3.0 | DOM樹變化時(shí)引發(fā)的事件 |
HTMLEvents | 2.0 | HTML4.01事件 |
Range | 2.0 | 用于操作DOM樹種某個(gè)范圍的對(duì)象和方法 |
Traversal | 2.0 | 遍歷DOM樹的方法 |
LS | 3.0 | 文件與DOM樹之間的同步加載和保存 |
LS-Asnyc | 3.0 | 文件與DOM樹之間的異步加載和保存 |
Validation | 3.0 | 在確保有效的前提下修改DOM樹的方法 |
hasFeature() 方法確實(shí)方便,但也有缺點(diǎn)。因?yàn)閷?shí)現(xiàn)者可以自行決定是否與DOM規(guī)范的不同部分保持一致。事實(shí)上,想讓hasFearture()針對(duì)所有值都有返回true很容易,但返回true有時(shí)候也不意味著實(shí)現(xiàn)與規(guī)范一致。
為此我們建議,在使用hasFreatrue()之外,還同時(shí)使用能力檢測(cè)。
文檔寫入write()和writeln()方法都接收一個(gè)字符串參數(shù),即要寫入到輸出流中的文本。wirte()會(huì)原樣寫入,而writeln()則會(huì)在字符串的末尾添加一個(gè)換行符(n)。在頁(yè)面加載的過程中,可以使用這兩個(gè)方法動(dòng)態(tài)的加入內(nèi)容。
在包含JavaScript文件時(shí),必須注意不能像下面的例子那樣直接包含字符串"",因?yàn)檫@會(huì)導(dǎo)致該字符串被解釋為腳本塊的結(jié)束,后面的代碼將無法執(zhí)行。使用轉(zhuǎn)義""可以避免這個(gè)問題。
open()和close()分別用于打開和關(guān)閉網(wǎng)頁(yè)的輸出流。如果是在頁(yè)面加載期間使用write()或writeln()方法,則不需要用到這兩個(gè)方法。
嚴(yán)格型XHTML文檔不支持文檔吸入。對(duì)于那些按照application/xml+xhtml內(nèi)容類型提供的頁(yè)面,這兩個(gè)方法也同樣無效。
Element類型Element類型用于表現(xiàn)XML或XHTML元素,提供了對(duì)元素標(biāo)簽名、子節(jié)點(diǎn)及特性的訪問。
Element類型具有以下特征:
nodeType的值為1
nodeName的值為元素的標(biāo)簽名
nodeValue的值為null
parentNode的值可能為Dcoment或Element
其子節(jié)點(diǎn)可能是 Element、 Text 、 Comment 、 ProcessingInstruction、 CDATASection、 EntityReference
訪問元素的標(biāo)簽名,可以使用nodeName屬性,也可以是使用tagName屬性,這兩個(gè)屬性會(huì)返回相同的值。
var div = document.getElementById("myDiv"); console.log(div.tagName); // "DIV" console.log(div.nodeName); // "DIV" console.log(div.tagName == div.nodeName); // true
if (element.tagName == "div") { // 不能這樣比較,很容易出錯(cuò) } if (element.tagName.toLowerCase() == "div") { // 推薦這樣做(適用于任何文檔) }HTML元素
所有HTML元素都由HTMLElement類型表示。HTMLElement類型直接繼承自Elment并添加了一些屬性。每個(gè)HTML元素中都存在的下列標(biāo)準(zhǔn)特性:
id 元素在文檔中的唯一標(biāo)識(shí)符
title 有關(guān)元素的附加說明信息,一般通過工具提示條顯示出來
lang 元素內(nèi)容的語言代碼,很少使用
dir 語言的方向值為"ltr"(left-to-right 從左至右)或 "rtl"
className 與元素的class特性對(duì)應(yīng),即為元素指定的CSS類。沒有將這個(gè)屬性命名為class是因?yàn)閏lass是ECMAScript的保留字。
并不是對(duì)所有屬性的修改都會(huì)在頁(yè)面中直觀的表現(xiàn)出來。對(duì)id或lang的修改對(duì)用戶而言是透明不可見的。而對(duì)title的修改則只會(huì)在鼠標(biāo)移動(dòng)到這個(gè)元素之上時(shí)才會(huì)顯示出來。對(duì)dir的修改會(huì)在屬性重寫的那一刻,立即影響頁(yè)面中文本的左右對(duì)齊方式。修改className時(shí),如果新類關(guān)聯(lián)了與此前不同的CSS樣式,就立即應(yīng)用新的樣式。
下面表格列出了所有HTML元素以及與之關(guān)聯(lián)的類型(以斜體印刷的元素表示不推薦使用了)。注意表中的這些類型在Opera、Safari、Chrome、Firefox中都可以通過JavaScript訪問,但在IE8之前的版本中,不能通過JavaScript訪問。
操作特性的DOM方法主要有三個(gè),分別是getAttribute()、setAttribute()、 removeAttribute()。
var div = document.getElemntByid("myDiv"); console.log(div.getAttribute("id")); // "myDiv" console.log(div.getAttribute("class")); // "bd" console.log(div.getAttribute("title")); // "Body Text" console.log(div.getAttribute("lang")); // "en" console.log(div.getAttribute("dir")); // "ltr"
注意,傳遞給getAttribute()的特性名與實(shí)際的特性名相同。因此想要得到class特性值,應(yīng)該傳入"class" 而不是"className",后者只在通過對(duì)象屬性訪問特性時(shí)才用。
如果給定的特性不存在,getAttribute()返回null。
也可以取得自定義特性,即標(biāo)準(zhǔn)HTML語言中沒有的特性的值。需要注意,特性的名稱不區(qū)分大小寫,即"ID" 和 "id" 代表的都是同一個(gè)特性。另外也要注意,根據(jù)HTML5規(guī)范,自定義特性應(yīng)該加上data-前綴以便驗(yàn)證。
任何元素的所有特性,也都可以通過DOM元素本身的屬性來訪問。當(dāng)然HTMLElement也會(huì)有5個(gè)屬性與相應(yīng)的特性一一對(duì)應(yīng)。不過只有公認(rèn)的(非自定義)特性才會(huì)以屬性的形式添加到DOM對(duì)象中。例如可以通過div.id訪問div元素的id屬性。不過自定義特性在Safari、Opera、Chrome、Firefox中是不存在的,但I(xiàn)E卻會(huì)為自定義特性也創(chuàng)建屬性。
CSS通過getAttribute()訪問時(shí),返回的style特性值中包含的是CSS文本,而通過屬性來訪問它則會(huì)返回一個(gè)對(duì)象。由于style屬性是用于以編程方式訪問元素樣式的(本章后面討論),因此并沒有直接映射到style特性。
時(shí)間處理程序(例如onclick)通過getAttribute()訪問,返回的是相應(yīng)的代碼字符串。而在訪問onclick屬性時(shí),則返回的是一個(gè)JavaScript函數(shù)(如果未在元素中指定相應(yīng)特性,則返回null)。這是因?yàn)閛nclick及其他事件程序?qū)傩员旧砭蛻?yīng)該被賦予函數(shù)值。
由于存在上述差別,在通過JavaScript以編程方式操作DOM時(shí),開發(fā)人員不經(jīng)常使用 getAttribute()方法,而只是使用對(duì)象的屬性。只有在取得自定義特性值得情況下,才會(huì)使用getAttribute()方法。
在IE7及以前版本中,通過getAttribute()訪問style特性或onclick,返回的值與屬性相同,都返回對(duì)象值或函數(shù)值。雖然IE8已經(jīng)修復(fù)了這個(gè)bug,但不同IE版本間的不一致性,也是導(dǎo)致開發(fā)人員不適用getAttribute()訪問HTML特性的一個(gè)原因。
設(shè)置特性與getAttribute()對(duì)應(yīng)的方法時(shí)setAttribute()這個(gè)方法接收兩個(gè)參數(shù):要設(shè)置的特性名和值。如果特性已經(jīng)存在,setAttribute()會(huì)以指定的值替換現(xiàn)有的值;如果特性不存在,則創(chuàng)建該屬性并設(shè)置相應(yīng)的值。
setAttribute()方法既可以操作HTML特性也可以操作自定義特性。通過這個(gè)方法設(shè)置的特性名會(huì)統(tǒng)一轉(zhuǎn)換為小寫形式,即"ID"最終變成"id"。
div.setAttribute("id", "someOtherId"); div.id = "someOtherId"; // 添加自定義屬性,該屬性不會(huì)自動(dòng)成為元素的特性 div.mycolor = "red"; div.getAttribute("mycolor"); // null ie除外
removeAttribute() 用于徹底刪除元素的特性,調(diào)用這個(gè)方法不僅會(huì)清楚特性的值,而且也會(huì)從元素中完全刪除特性。這個(gè)方法并不常用,IE6及以前版本不支持。
div.removeAttribute("class");attributes屬性
Element 類型是使用 attributes 屬性的唯一一個(gè)DOM節(jié)點(diǎn)類型 。
attributes屬性中包含一個(gè)NamedNodeMap,與NodeList類似,也是一個(gè)動(dòng)態(tài)集合。元素的每一個(gè) 特性都由一個(gè)Attr節(jié)點(diǎn)表示,每個(gè)節(jié)點(diǎn)都保存在NamedNodeMap對(duì)象中。
NamedNodeMap對(duì)象擁有以下方法
getNamedItem(name):返回nodeName屬性等于name的節(jié)點(diǎn)
removeNamedItem(name):從列表中移除nodeName屬性 等于name的節(jié)點(diǎn)
setNameItem(node):向列表中添加節(jié)點(diǎn),以節(jié)點(diǎn)的nodeName屬性為索引
item(pos):返回位于數(shù)字pos位置處的節(jié)點(diǎn)
attributes屬性中包含一系列節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)的nodeName就是特性的名稱,而節(jié)點(diǎn)的nodeValue就是特性的值。
// 取得元素的id var id = element.attributes.getNamedItem("id").nodeValue; // 設(shè)置元素的id element.attributes["id"].nodeValue = "someOtherId"; // 刪除元素id,并返回被刪除特性的Attr節(jié)點(diǎn) var oldAttr = element.attributes.removeNamedItem("id"); // 傳入一個(gè)新的特性節(jié)點(diǎn) element.attributes.setNameItem(newAttr);
由于attributes的方法不夠方便,因此開啊人員更多的會(huì)使用getAttribute()、removeAttribute()、setAttribute()方法。如果想要遍歷元素特性,可以用attributes
針對(duì)attributes對(duì)象中的特性,不同瀏覽器返回的順序不同。
IE7及更早版本返回HTML元素中所有可能的特性,包括沒有指定的特性。返回100多個(gè)特性是常見的
// 迭代元素的每一個(gè)特性,然后構(gòu)造成 name="value"字符串 function outputAttributes(element) { var pairs = new Array(), attrName, attrValue, i, len; for (i=0, len=elment.attributes.length; i < len; i++) { attrName = element.attributes[i].nodeName; attrValue = element.attributes[i].nodeValue; // 針對(duì) IE7- 做兼容 // 根據(jù)specified屬性,只返回指定的特性 if (element.attributes[i].specified) { paris.push(attrName + "="" + attrValue + """); } } return pairs.join(" "); }創(chuàng)建元素
document.createElement()方法可以創(chuàng)建新元素。只接收一個(gè)參數(shù),即要?jiǎng)?chuàng)建元素的標(biāo)簽名,在HTML文檔中不區(qū)分大小寫,而在XML(包括XHTML)文檔中,則是區(qū)分大小寫。
document.createElement()創(chuàng)建元素的同時(shí),也為新元素設(shè)置了ownerDcoument屬性。此時(shí)還可以操作元素的特性,為它添加更多子節(jié)點(diǎn)。
由于新元素尚未被添加到文檔樹中,因此設(shè)置這些特性不會(huì)影響瀏覽器的顯示。要把新元素添加到文檔樹,可以使用appendChild() insertBefore() replaceChild()方法。
// 創(chuàng)建 var div = document.createElement("div"); // 操作元素特性,添加子節(jié)點(diǎn) div.id = "myNewDiv"; div.className = "box"; document.body.appendChild(div);
在IE中可以傳入完整的元素標(biāo)簽,也可以包含屬性(僅IE支持)。這樣有助于避開在IE7及更早版本中動(dòng)態(tài)創(chuàng)建元素的某些問題:
不能設(shè)置動(dòng)態(tài)創(chuàng)建的元素的name特性
不能通過表單的reset()方法重設(shè)動(dòng)態(tài)創(chuàng)建的元素(第13章討論reset()方法)
動(dòng)態(tài)創(chuàng)建的type特性值為“reset”的元素重設(shè)不了表單
動(dòng)態(tài)創(chuàng)建的一批name相同的單選按鈕彼此毫無關(guān)系。
if (client.browser.id && client.browser.ie <= 7) { var div = document.createElement(""); }元素的子節(jié)點(diǎn)
元素可以有任意書目的子節(jié)點(diǎn)和后臺(tái)節(jié)點(diǎn),因?yàn)樵乜梢允瞧渌氐淖庸?jié)點(diǎn)。元素的childNodes屬性中包含了它所有子節(jié)點(diǎn),這些子節(jié)點(diǎn)可能是元素、文本節(jié)點(diǎn)、注釋或處理指令。不用瀏覽器在看待這些節(jié)點(diǎn)方面存在顯著的不同。
IE解析,元素會(huì)有3個(gè)子節(jié)點(diǎn),分別是3個(gè)元素。但如果是其他瀏覽器,
元素都會(huì)有7個(gè)元素,包括3個(gè)元素和4個(gè)文本節(jié)點(diǎn)(表示元素之間的空白符)。
如果將元素間的空白符刪除,那么所有瀏覽器都會(huì)返回相同數(shù)目的子節(jié)點(diǎn)
如果需要通過childNodes屬性遍歷子節(jié)點(diǎn),那么一定不要忘記瀏覽器間的這一差別。這意味著在執(zhí)行某項(xiàng)操作以前,通常都要先檢查nodeType屬性
for (var i=0, len = element.childNodes.length; i < len; i++) { if (element.childNodes[i].nodeTpe == 1) { ... } }
如果想通過某個(gè)特性的標(biāo)簽名取得子節(jié)點(diǎn)或后代節(jié)點(diǎn),可以通過元素調(diào)用getElementsByTagName()方法,結(jié)果只會(huì)返回當(dāng)前元素的后代。
var ul = document.getElementById("myList"); var items = ul.getElementsByTagName("li");Text類型
文本節(jié)點(diǎn)由Text類型表示,包含的是可以照字面量解釋的純文本內(nèi)容。純文本中可以包含轉(zhuǎn)義后的HTML字符,但不能包含HTML代碼。
Text節(jié)點(diǎn)具有以下特征:
nodeType的值為3
nodeName的值為"#text"
nodeValue的值為節(jié)點(diǎn)所包含的文本
parentNode是一個(gè)Element
不支持(沒有)子節(jié)點(diǎn)
可以通過nodeValue屬性或data屬性訪問Text節(jié)點(diǎn)中包含的文本,這兩個(gè)屬性的值相同。對(duì)nodeValue的修改也會(huì)通過data反映出來,反之亦然。
使用下列方法可以操作節(jié)點(diǎn)中的文本
appendData(text):將text添加到節(jié)點(diǎn)的末尾
deleteData(offset, count):從offset指定的位置插入text
insertData(offset, text):在offset指定的位置插入text
replaceData(offset, count, text):用text替換從offset指定的位置開始到 offset+count為止處的文本
splitText(offset):從offset指定的位置將當(dāng)前文本節(jié)點(diǎn)分成兩個(gè)文本節(jié)點(diǎn)。
substringData(offset, count):提取從offset指定的位置開始到 offset+count為止處的字符串
length屬性:保存著節(jié)點(diǎn)中字符的書目。而且nodeValue.length和data.length中也保存著同樣的數(shù)值
在默認(rèn)情況下,每個(gè)可以包含內(nèi)容的元素最多只能有一個(gè)文本節(jié)點(diǎn),而且必須確實(shí)有內(nèi)容存在
Hello World!
// 可以像這樣取得文本子節(jié)點(diǎn) var textNode= div.firstChild; // 或者 div.childNodes[0] // 取得文本節(jié)點(diǎn)的引用后,就可以修改它了 div.firstChild.nodeValue = "Some other message";
如果這個(gè)文本節(jié)點(diǎn)當(dāng)前存在于文檔樹中,那么修改文本節(jié)點(diǎn)的結(jié)果就會(huì)立即得到反映。
修改文本節(jié)點(diǎn)時(shí),字符串會(huì)經(jīng)過HTML(或XML,取決于文檔類型)編碼。換言之,小于號(hào)、大于號(hào)或引號(hào)都會(huì)像下面的例子一樣被轉(zhuǎn)義
div.firstChild.nodeValue = "Some other message"; // 輸出結(jié)果:"Some other message"
這是在向DOM文檔中插入文本之前,先對(duì)其進(jìn)行HTML編碼的一種有效方式
創(chuàng)建文本節(jié)點(diǎn)document.createTextNode()創(chuàng)建新的文本節(jié)點(diǎn)。與設(shè)置已有文本節(jié)點(diǎn)的值一樣,作為參數(shù)的文本也將按照HTML或XML的格式進(jìn)行編碼。
var textNode = document.createTextNode("Hello World!");
在創(chuàng)建新文本節(jié)點(diǎn)的同時(shí),也會(huì)為其設(shè)置ownerDocument屬性。不過除非把新節(jié)點(diǎn)添加到文檔樹中已經(jīng)存在的節(jié)點(diǎn)中,否則我們不會(huì)在瀏覽器窗口中看到新節(jié)點(diǎn)。
var element = document.createElement("div"); elment.className = "message"; var textNode = document.createTextNode("Hello world!"); element.appendChild(textNode); document.body.appendChild(element);
一般情況下,每個(gè)元素只有一個(gè)文本子節(jié)點(diǎn)。不過在某些情況下也可能包含多個(gè)文字子節(jié)點(diǎn)。相鄰的同胞文本節(jié)點(diǎn),之間會(huì)連起來,中間不會(huì)有空格。
var element = document.createElement("div"); elment.className = "message"; var textNode = document.createTextNode("Hello world!"); element.appendChild(textNode); var anotherTextNode = document.createTextNode("Yippee!"); element.appendChild(anotherTextNode); document.body.appendChild(element);規(guī)范化文本節(jié)點(diǎn)
DOM文檔中存在相鄰的同胞文本節(jié)點(diǎn)很容易導(dǎo)致混亂,因?yàn)榉植磺逦谋竟?jié)點(diǎn)之間的界限。于是催生了一個(gè)能夠?qū)⑾噜徫谋竟?jié)點(diǎn)合并的方法。
normalize()方法是由Node類型定義的(因而在所有節(jié)點(diǎn)類型中都存在)。如果在一個(gè)包含多個(gè)文本節(jié)點(diǎn)的父元素上調(diào)用normalize()方法,則會(huì)將所有文本節(jié)點(diǎn)合并成一個(gè)文本節(jié)點(diǎn)。
var element = document.createElement("div"); elment.className = "message"; var textNode = document.createTextNode("Hello world!"); element.appendChild(textNode); var anotherTextNode = document.createTextNode("Yippee!"); element.appendChild(anotherTextNode); document.body.appendChild(element); console.log(element.childNodes.length); // 2 element.normalize(); console.log(element.childNodes.length); // 1 console.log(element.firstChild.nodeValue); // "Hello World!Yippee!"
瀏覽器在解析文檔時(shí)永遠(yuǎn)不會(huì)創(chuàng)建相鄰的文本節(jié)點(diǎn),這種情況只會(huì)作為DOM操作的結(jié)果出現(xiàn)。
normalize()有時(shí)候會(huì)導(dǎo)致IE6崩潰,IE7以上修復(fù)了此問題。
分割文本節(jié)點(diǎn)splitText()方法會(huì)將一個(gè)文本節(jié)點(diǎn)分割成兩個(gè)。
var element = document.createElement("div"); elment.className = "message"; var textNode = document.createTextNode("Hello world!"); element.appendChild(textNode); document.body.appendChild(element); var newNode = element.firstChild.splitText(5); console.log(element.firstChild.nodeValue); // "Hello" console.log(newNode.nodeValue); // " World!" console.log(element.childNodes.length); // 2Comment類型
注釋在DOM中是通過Comment類型來表示的。Comment節(jié)點(diǎn)具有以下特征:
nodeType的值為8
nodeName的值為 "#comment"
nodeValue的值是注釋的內(nèi)容
parentNode可能是Dcoment或Element
不支持(沒有)子節(jié)點(diǎn)
Comment類型與Text類型繼承自相同的基類,因此它擁有除splitText()之外的所有字符串操作方法。
var div = document.getElementById("myDiv"); var comment = div.firstChild; console.log(comment.data); // "A comment"
使用document.createComment()并為其傳遞注釋文本也可以創(chuàng)建注釋節(jié)點(diǎn)
var comment = document.createComment("A comment ");
開發(fā)人員很少會(huì)創(chuàng)建和訪問注釋節(jié)點(diǎn),此外瀏覽器也不會(huì)識(shí)別位于標(biāo)簽后的注釋。如果要訪問注釋節(jié)點(diǎn),一定要保證它們是位于和之間。
CDATASection類型CDATASection類型只針對(duì)基于XML的文檔,表示的是CDATA區(qū)域。與Comment類似、CDATASection類型繼承自Text類型,因此擁有除splitText()之外的所有字符串操作方法。
CDATASection節(jié)點(diǎn)具有下列特征:
nodeType的值為4
nodeName的值為"#cdata-section"
nodeValue的值是CDATA區(qū)域中的內(nèi)容
parentNode可能是Document或Element
不支持(沒有)子節(jié)點(diǎn)
CDATA區(qū)域只會(huì)出現(xiàn)在XML文檔中,因此多數(shù)瀏覽器都會(huì)把CDATA區(qū)域錯(cuò)誤的解析為Comment或Element。
這個(gè)例子中div元素應(yīng)該包含一個(gè)CDATASection節(jié)點(diǎn)。但四大主流瀏覽器都不能正確解析。即使對(duì)于有效的XHTML頁(yè)面,瀏覽器也沒有正確的支持嵌入的CDATA區(qū)域。
在真正的XML文檔中,可以使用document.createCDataSection()來創(chuàng)建CDATA區(qū)域。
DocumentType類型
DocumentType類型在Web瀏覽器中并不常用,僅有 Firefox Safari 和 Opera支持它。
nodeType的值為10
nodeName的值為doctype的名稱
nodeValue的值是null
parentNode是Document
不支持(沒有)子節(jié)點(diǎn)
通常,瀏覽器中的文檔使用的都是HTML或XHTML文檔類型,只有name屬性是有用的。
console.log(document.doctype.name); // "HTML"
IE不支持DocumentType,因此 document.doctype的值始終都是null
DocumentFragment 類型所有節(jié)點(diǎn)類型中,只有DocumentFragment在文檔中沒有對(duì)應(yīng)的標(biāo)記。
DOM規(guī)定文檔片段(document fragment)是一種輕量級(jí)的文檔,可以包含和控制節(jié)點(diǎn),但不會(huì)像完整的文檔那樣占用額外的資源。
nodeType的值為11
nodeName的值為"#document-fragment"
nodeValue的值是null
parentNode是null
子節(jié)點(diǎn)可以是Element 、ProcessingInstruction 、Comment 、Text、 CDATASection 、EntityReference
雖然不能把文檔文段直接添加到文檔中,但可以將它作為一個(gè)倉(cāng)庫(kù)來使用,在里面保存將來可能會(huì)添加到文檔中的節(jié)點(diǎn)。
document.createDocumentFragment() 方法創(chuàng)建文檔片段
var fragment = document.createDocumentFragment(); var ul = document.getElementById("myList"); var li = null; // 如果直接向ul添加li元素會(huì)導(dǎo)致瀏覽器反復(fù)渲染 // fragment作為一個(gè)元素中轉(zhuǎn)的倉(cāng)庫(kù)避免了這個(gè)問題 for (var i=0; i < 3; i++) { li = document.createElement("li"); li.appendChild(document.createTextNode("Item " + (i+1))); fragment.appendChild(li); } // 這里只會(huì)將fragment的所有子節(jié)點(diǎn)添加到ul上 // 而fragment本身永遠(yuǎn)不會(huì)成為文檔樹的一部分 ul.appendChild(fragment);Attr類型
元素的特性在DOM中以Attr類型來表示。在所有瀏覽器中(包括IE8),都可以訪問 Attr類型的構(gòu)造函數(shù)和原型。
nodeType的值為2
nodeName的值就是特性的名稱
nodeValue的值就是特性的值
parentNode是null
HTML中不支持(沒有)子節(jié)點(diǎn)
XML中子節(jié)點(diǎn)可以是Text或EntityReference
盡管Attr是節(jié)點(diǎn),但特性卻不被認(rèn)為是DOM文檔樹的一部分。
Attr對(duì)象有三個(gè)屬性:name value specified。
document.createAttribute()傳入特性的名稱可以創(chuàng)建新的特性節(jié)點(diǎn)。
var attr = document.createAttribute("align"); attr.value = "left"; element.setAttribute(attr); console.log(element.attributes["align"].value); // left console.log(element.getAttributeNode("align").value); // left console.log(element.getAttribute("align")); // leftDOM操作技術(shù) 動(dòng)態(tài)腳本
使用元素可以向頁(yè)面中插入JavaScript代碼,一種是通過其src特性包含外部文件,另一種就是用這個(gè)元素本身包含代碼。
動(dòng)態(tài)加載的JavaScript文件能夠立即運(yùn)行。
// 在執(zhí)行最后一行代碼把
遺憾的是,并沒有什么標(biāo)準(zhǔn)方式來探知腳本是否加載完成。
從邏輯上講,使用行內(nèi)方式直接插入代碼是有效的。在Firefox Safari Chrome Opera中,都可以正常運(yùn)行,但在IE中,會(huì)導(dǎo)致錯(cuò)誤。IE將視為一個(gè)特殊元素,不允許DOM訪問其子節(jié)點(diǎn)。不過可以使用元素的text屬性來制定JavaScript代碼
var script = document.createElement("script"); script.type = "text/javascript"; // 這樣IE不支持 script.appendChild( document.createTextNode("function sayHi() { console.log("Hi")}") ); // 可以使用`