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