九择份、DOM

??DOM(文檔對(duì)象模型)是針對(duì) HTML 和 XML 文檔的一個(gè) API(應(yīng)用程序編程接口)。

??DOM 描繪了一個(gè)層次化的節(jié)點(diǎn)樹(shù)良瞧,允許開(kāi)發(fā)人員添加、移除和修改頁(yè)面的某一部分训唱。

??DOM 脫胎于 Netscape 及微軟公司創(chuàng)始的 DHTML(動(dòng)態(tài) HTML)褥蚯,但現(xiàn)在它已經(jīng)成為表現(xiàn)和操作頁(yè)面標(biāo)記的真正的跨平臺(tái)、語(yǔ)言中立的方式况增。

??1998 年 10 月 DOM 1 級(jí)規(guī)范成為 W3C 的推薦標(biāo)準(zhǔn)赞庶,為基本的文檔結(jié)構(gòu)及查詢提供了接口。

??IE澳骤、Firefox歧强、Safari、Chrome 和 Opera 都非常完善地實(shí)現(xiàn)了 DOM为肮。

??注意摊册,IE 中的所有 DOM 對(duì)象都是以 COM 對(duì)象的形式實(shí)現(xiàn)的。這意味著 IE 中的 DOM 對(duì)象與原生 JavaScript 對(duì)象的行為或活動(dòng)特點(diǎn)并不一致颊艳。

1丧靡、節(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)的樹(shù)形結(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)卤恳,如下圖所示:

??文檔節(jié)點(diǎn)是每個(gè)文檔的根節(jié)點(diǎn)。在上述例子中寒矿,文檔節(jié)點(diǎn)只有一個(gè)突琳,即 <html> 元素,我們稱之為文檔元素符相。

??文檔元素是文檔的最外層元素拆融,文檔中的其他元素都包含在文檔元素中。每個(gè)文檔只能有一個(gè)文檔元素啊终。在 HTML 頁(yè)面中镜豹,文檔元素始終都是 <html> 元素。在 XML 中蓝牲,沒(méi)有預(yù)定義的元素趟脂,因此任何元素都可能成為文檔元素。

??每一段標(biāo)記都可以通過(guò)樹(shù)中的一個(gè)節(jié)點(diǎn)表示:HTML 元素通過(guò)元素節(jié)點(diǎn)表示,特性(attribute)通過(guò)特性節(jié)點(diǎn)表示,文檔類型通過(guò)文檔類型節(jié)點(diǎn)表示敢会,而注釋則通過(guò)注釋節(jié)點(diǎn)表示忌傻。

??總共有 12 種節(jié)點(diǎn)類型,這些類型都繼承自一個(gè)基類型波桩。

1.1、Node 類型

??DOM 1 級(jí)定義了一個(gè) Node 接口,該接口將由 DOM 中的所有節(jié)點(diǎn)類型實(shí)現(xiàn)欠动。這個(gè) Node 接口在JavaScript 中是作為 Node 類型實(shí)現(xiàn)的;除了 IE 之外惑申,在其他所有瀏覽器中都可以訪問(wèn)到這個(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.");
} 

??上述例子比較了 someNode.nodeType 與 Node.ELEMENT_NODE 常量。如果二者相等鞋既,則意味著 someNode 確實(shí)是一個(gè)元素力九。
??然而,由于 IE 還沒(méi)有公開(kāi) Node 類型的構(gòu)造函數(shù)涛救,因此上面的代碼在 IE 中會(huì)導(dǎo)致錯(cuò)誤畏邢。為了確保跨瀏覽器兼容检吆,最好還是將 nodeType 屬性與數(shù)字值進(jìn)行比較舒萎,示例:

if (someNode.nodeType == 1){ // 適用于所有瀏覽器
    console.log("Node is an element.");
} 

??并不是所有節(jié)點(diǎn)類型都受到 Web 瀏覽器的支持。開(kāi)發(fā)人員最常用的就是元素和文本節(jié)點(diǎn)蹭沛。

1. 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)簽名
} 

??在上述例子中帚呼,首先檢查節(jié)點(diǎn)類型掏缎,看它是否是一個(gè)元素。如果是煤杀,則取得并保存 nodeName 的值眷蜈。
??對(duì)于元素節(jié)點(diǎn),nodeName 中保存的始終都是元素的標(biāo)簽名沈自,而 nodeValue 的值則始終為 null酌儒。

2. 節(jié)點(diǎn)關(guān)系

??文檔中所有的節(jié)點(diǎn)之間都存在這樣或那樣的關(guān)系。

??節(jié)點(diǎn)間的各種關(guān)系可以用傳統(tǒng)的家族關(guān)系來(lái)描述枯途,相當(dāng)于把文檔樹(shù)比喻成家譜忌怎。在 HTML 中,可以將 <body> 元素看成是 <html> 元素的子元素酪夷;相應(yīng)地榴啸,也就可以將 <html> 元素看成是 <body> 元素的父元素。而 <head> 元素晚岭,則可以看成是 <body> 元素的同胞元素插掂,因?yàn)樗鼈兌际峭粋€(gè)父元素 <html> 的直接子元素。

??每個(gè)節(jié)點(diǎn)都有一個(gè) childNodes 屬性,其中保存著一個(gè) NodeList 對(duì)象辅甥。NodeList 是一種類數(shù)組對(duì)象,用于保存一組有序的節(jié)點(diǎn)燎竖,可以通過(guò)位置來(lái)訪問(wèn)這些節(jié)點(diǎn)璃弄。
??請(qǐng)注意,雖然可以通過(guò)方括號(hào)語(yǔ)法來(lái)訪問(wèn) 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ì)象中。
??我們常說(shuō)借跪,NodeList 是有生命政己、有呼吸的對(duì)象,而不是在我們第一次訪問(wèn)它們的某個(gè)瞬間拍攝下來(lái)的一張快照掏愁。
??下面的例子展示了如何訪問(wèn)保存在 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() 方法都沒(méi)有問(wèn)題果港,但使用方括號(hào)語(yǔ)法看起來(lái)與訪問(wèn)數(shù)組相似沦泌,因此頗受一些開(kāi)發(fā)人員的青睞。
??另外辛掠,要注意 length 屬性表示的是訪問(wèn) NodeList 的那一刻谢谦,其中包含的節(jié)點(diǎn)數(shù)量。
??我們知道萝衩,對(duì) arguments 對(duì)象使用 Array.prototype.slice() 方法可以將其轉(zhuǎn)換為數(shù)組回挽。而采用同樣的方法,也可以將 NodeList 對(duì)象轉(zhuǎn)換為數(shù)組欠气。來(lái)看下面的例子:

// 在 IE8 及之前版本中無(wú)效
var arrayOfNodes = Array.prototype.slice.call(someNode.childNodes, 0); 

??除 IE8 及更早版本之外厅各,這行代碼能在任何瀏覽器中運(yùn)行。由于 IE8 及更早版本將 NodeList 實(shí)現(xiàn)為一個(gè) COM 對(duì)象预柒,而我們不能像使用 JScript 對(duì)象那樣使用這種對(duì)象队塘,因此上面的代碼會(huì)導(dǎo)致錯(cuò)誤。
??要想在 IE 中將 NodeList 轉(zhuǎn)換為數(shù)組宜鸯,必須手動(dòng)枚舉所有成員憔古。下列代碼在所有瀏覽器中都可以運(yùn)行:

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è) convertToArray() 函數(shù)首先嘗試了創(chuàng)建數(shù)組的最簡(jiǎn)單方式。如果導(dǎo)致了錯(cuò)誤(說(shuō)明是在 IE8 及更早版本中)淋袖,則通過(guò) try-catch 塊來(lái)捕獲錯(cuò)誤鸿市,然后手動(dòng)創(chuàng)建數(shù)組。這是另一種檢測(cè)怪癖的形式。

??每個(gè)節(jié)點(diǎn)都有一個(gè) parentNode 屬性焰情,該屬性指向文檔樹(shù)中的父節(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 屬性验游,可以訪問(wèn)同一列表中的其他節(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)峦阁。如果沒(méi)有子節(jié)點(diǎn),那么 firstChild 和 lastChild 的值均為 null耘成。明確這些關(guān)系能夠?qū)ξ覀儾檎液驮L問(wèn)文檔結(jié)構(gòu)中的節(jié)點(diǎn)提供極大的便利榔昔。
??下圖形象地展示了上述關(guān)系。

??在反映這些關(guān)系的所有屬性當(dāng)中瘪菌,childNodes 屬性與其他屬性相比更方便一些撒会,因?yàn)橹豁毷褂煤?jiǎn)單的關(guān)系指針,就可以通過(guò)它訪問(wèn)文檔樹(shù)中的任何節(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á)頂端闸英,而是可以直接訪問(wèn)文檔節(jié)點(diǎn)。

雖然所有節(jié)點(diǎn)類型都繼承自 Node介袜,但并不是每種節(jié)點(diǎn)都有子節(jié)點(diǎn)甫何。

3. 操作關(guā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)竹勉。示例:

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 樹(shù)看成是由一系列指針連接起來(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() 方法。
??insertBefore() 方法接受兩個(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ù)中被移除肖抱,同時(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)沒(méi)有了自己的位置拌屏。

??如果只想移除而非替換節(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)沒(méi)有了自己的位置舱权。

??前面介紹的四個(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ā)生。

4. 其他方法

??有兩個(gè)方法是所有類型的節(jié)點(diǎn)都有的脖捻。

??第一個(gè)就是 cloneNode()阔逼,用于創(chuàng)建調(diào)用這個(gè)方法的節(jié)點(diǎn)
的一個(gè)完全相同的副本。
??cloneNode() 方法接受一個(gè)布爾值參數(shù)郭变,表示是否執(zhí)行深復(fù)制颜价。在參數(shù)為 true的情況下,執(zhí)行深復(fù)制诉濒,也就是復(fù)制節(jié)點(diǎn)及其整個(gè)子節(jié)點(diǎn)樹(shù)周伦;在參數(shù)為 false 的情況下,執(zhí)行淺復(fù)制未荒,即只復(fù)制節(jié)點(diǎn)本身专挪。
??復(fù)制后返回的節(jié)點(diǎn)副本屬于文檔所有,但并沒(méi)有為它指定父節(jié)點(diǎn)片排。因此寨腔,這個(gè)節(jié)點(diǎn)副本就成為了一個(gè)“孤兒”,除非通過(guò) appendChild()率寡、insertBefore() 或 replaceChild() 將它添加到文檔中迫卢。例如,假設(shè)有下面的 HTML 代碼冶共。

<ul>
    <li>item 1</li>
    <li>item 2</li>
    <li>item 3</li>
</ul> 

??如果我們已經(jīng)將 <ul> 元素的引用保存在了變量 myList 中乾蛤,那么通常下列代碼就可以看出使用 cloneNode() 方法的兩種模式每界。

var deepList = myList.cloneNode(true);
console.log(deepList.childNodes.length); // 3(IE < 9)或 7(其他瀏覽器)

var shallowList = myList.cloneNode(false);
console.log(shallowList.childNodes.length); // 0 

??在上述例子中,deepList 中保存著一個(gè)對(duì) myList 執(zhí)行深復(fù)制得到的副本家卖。因此眨层,deepList 中包含 3 個(gè)列表項(xiàng),每個(gè)列表項(xiàng)中都包含文本上荡。
??而變量 shallowList 中保存著對(duì) myList 執(zhí)行淺復(fù)制得到的副本趴樱,因此它不包含子節(jié)點(diǎn)。
??deepList.childNodes.length 中的差異主要是因?yàn)?IE8 及更早版本與其他瀏覽器處理空白字符的方式不一樣酪捡。IE9 之前的版本不會(huì)為空白符創(chuàng)建節(jié)點(diǎn)叁征。

??cloneNode() 方法不會(huì)復(fù)制添加到 DOM 節(jié)點(diǎn)中的 JavaScript 屬性,例如事件處理程序等逛薇。
??這個(gè)方法只復(fù)制特性航揉、(在明確指定的情況下也復(fù)制)子節(jié)點(diǎn),其他一切都不會(huì)復(fù)制金刁。
??IE 在此存在一個(gè) bug,即它會(huì)復(fù)制事件處理程序议薪,所以我們建議在復(fù)制之前最好先移除事件處理程序尤蛮。

??我們要介紹的最后一個(gè)方法是 normalize(),這個(gè)方法唯一的作用就是處理文檔樹(shù)中的文本節(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)液肌。

1.2挟炬、Document 類型

??JavaScript 通過(guò) Document 類型表示文檔睬魂。在瀏覽器中尊沸,document 對(duì)象是 HTMLDocument(繼承自 Document 類型)的一個(gè)實(shí)例,表示整個(gè) HTML 頁(yè)面甜孤。
??而且老速,document 對(duì)象是 window 對(duì)象的一個(gè)屬性粥喜,因此可以將其作為全局對(duì)象來(lái)訪問(wèn)。
??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ò),最常見(jiàn)的應(yīng)用還是作為 HTMLDocument 實(shí)例的 document 對(duì)象芥丧。通過(guò)這個(gè)文檔對(duì)象紧阔,不僅可以取得與頁(yè)面有關(guān)的信息,而且還能操作頁(yè)面的外觀及其底層結(jié)構(gòu)续担。

?? Firefox擅耽、Safari、Chrome 和 Opera 中物遇,可以通過(guò)腳本訪問(wèn) Document 類型的構(gòu)造函數(shù)和原型乖仇。但在所有瀏覽器中都可以訪問(wèn) HTMLDocument 類型的構(gòu)造函數(shù)和原型,包括 IE8及后續(xù)版本询兴。

1. 文檔的子節(jié)點(diǎn)

??雖然 DOM 標(biāo)準(zhǔn)規(guī)定 Document 節(jié)點(diǎn)的子節(jié)點(diǎn)可以是 DocumentType乃沙、Element、ProcessingInstruction 或 Comment诗舰,但還有兩個(gè)內(nèi)置的訪問(wèn)其子節(jié)點(diǎn)的快捷方式警儒。
??第一個(gè)就是 documentElement 屬性,該屬性始終指向 HTML 頁(yè)面中的 <html> 元素眶根。另一個(gè)就是通過(guò) childNodes 列表訪問(wèn)文檔元素蜀铲,但通過(guò) documentElement 屬性則能更快捷、更直接地訪問(wèn)該元素属百。以下面這個(gè)簡(jiǎn)單的頁(yè)面為例记劝。

<html>
    <body>

    </body>
</html>

??這個(gè)頁(yè)面在經(jīng)過(guò)瀏覽器解析后,其文檔中只包含一個(gè)子節(jié)點(diǎn)诸老,即 <html> 元素隆夯。可以通過(guò) documentElement 或 childNodes 列表來(lái)訪問(wèn)這個(gè)元素别伏,如下所示蹄衷。

var html = document.documentElement; // 取得對(duì)<html>的引用
console.log(html === document.childNodes[0]); // true
console.log(html === document.firstChild); // true 

??上述例子說(shuō)明,documentElement厘肮、firstChild 和 childNodes[0] 的值相同愧口,都指向 <html> 元素。
??作為 HTMLDocument 的實(shí)例类茂,document 對(duì)象還有一個(gè) body 屬性耍属,直接指向 <body> 元素托嚣。因?yàn)殚_(kāi)發(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)訪問(wèn)它的信息。

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] 訪問(wèn)同一個(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è)屬性的用處很有限。
??從技術(shù)上說(shuō)士八,出現(xiàn)在 <html> 元素外部的注釋應(yīng)該算是文檔的子節(jié)點(diǎn)容燕。然而,不同的瀏覽器在是否解析這些注釋以及能否正確處理它們等方面婚度,也存在很大差異蘸秘。以下面簡(jiǎn)單的 HTML 頁(yè)面為例。

<!--第一條注釋 -->
<html>
    <body>

    </body>
</html>
<!--第二條注釋 --> 

??看起來(lái)這個(gè)頁(yè)面應(yīng)該有 3 個(gè)子節(jié)點(diǎn):注釋蝗茁、<html>元素醋虏、注釋。從邏輯上講哮翘,我們會(huì)認(rèn)為 document.childNodes 中應(yīng)該包含與這 3 個(gè)節(jié)點(diǎn)對(duì)應(yīng)的 3 項(xiàng)颈嚼。但是,現(xiàn)實(shí)中的瀏覽器在處理位于 <html> 外部的注釋方面存在如下差異饭寺。

  • IE8 及之前版本阻课、Safari 3.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)建為 document.childNodes 中的一個(gè)注釋節(jié)點(diǎn)署驻,也會(huì)將第二條注釋創(chuàng)建為 document.childNodes 中的注釋子節(jié)點(diǎn)奋献。
  • Firefox 以及 Safari 3.1 之前的版本會(huì)完全忽略這兩條注釋。

??同樣硕舆,瀏覽器間的這種不一致性也導(dǎo)致了位于 <html> 元素外部的注釋沒(méi)有什么用處秽荞。
??多數(shù)情況下,我們都用不著在 document 對(duì)象上調(diào)用 appendChild()抚官、removeChild() 和 replaceChild() 方法扬跋,因?yàn)槲臋n類型(如果存在的話)是只讀的,而且它只能有一個(gè)元素子節(jié)點(diǎn)(該節(jié)點(diǎn)通常早就已經(jīng)存在了)凌节。

2. 文檔信息

??作為 HTMLDocument 的一個(gè)實(shí)例钦听,document 對(duì)象還有一些標(biāo)準(zhǔn)的 Document 對(duì)象所沒(méi)有的屬性。
??這些屬性提供了 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)題欄中痪宰。修改 title 屬性的值不會(huì)改變<title>元素。來(lái)看下面的例子畔裕。

// 取得文檔標(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甜无。在沒(méi)有來(lái)源頁(yè)面的情況下扛点,referrer 屬性中可能會(huì)包含空字符串。
??所有這些信息都存在于請(qǐng)求的 HTTP 頭部岂丘,只不過(guò)是通過(guò)這些屬性讓我們能夠在 JavaScrip 中訪問(wèn)它們而已陵究,如下面的例子所示。

// 取得完整的 URL
var url = document.URL;

// 取得域名
var domain = document.domain;

// 取得來(lái)源頁(yè)面的 URL
var referrer = document.referrer; 

??URL 與 domain 屬性是相互關(guān)聯(lián)的奥帘。例如畔乙,如果 document.URL 等于 http://www.wrox.com/WileyCDA/,那么 document.domain 就等于 www.wrox.com翩概。
??在這 3 個(gè)屬性中牲距,只有 domain 是可以設(shè)置的返咱。但由于安全方面的限制,也并非可以給 domain 設(shè)置任何值牍鞠。
??如果 URL 中包含一個(gè)子域名咖摹,例如 p2p.wrox.com,那么就只能將 domain 設(shè)置為"wrox.com"(URL 中包含 "www"难述,如 www.wrox.com 時(shí)萤晴,也是如此)。不能將這個(gè)屬性設(shè)置為 URL 中不包含的域胁后,如下面的例子所示店读。

// 假設(shè)頁(yè)面來(lái)自 p2p.wrox.com 域
document.domain = "wrox.com"; // 成功
document.domain = "nczonline.net"; // 出錯(cuò)!

??當(dāng)頁(yè)面中包含來(lái)自其他子域的框架或內(nèi)嵌框架時(shí)攀芯,能夠設(shè)置 document.domain 就非常方便了屯断。
??由于跨域安全限制,來(lái)自不同子域的頁(yè)面無(wú)法通過(guò) JavaScript 通信侣诺。而通過(guò)將每個(gè)頁(yè)面的 document.domain 設(shè)置為相同的值殖演,這些頁(yè)面就可以互相訪問(wèn)對(duì)方包含的 JavaScript 對(duì)象了。
??例如年鸳,假設(shè)有一個(gè)頁(yè)面加載自 www.wrox.com趴久,其中包含一個(gè)內(nèi)嵌框架,框架內(nèi)的頁(yè)面加載自 p2p.wrox.com搔确。由于 document.domain 字符串不一樣彼棍,內(nèi)外兩個(gè)頁(yè)面之間無(wú)法相互訪問(wèn)對(duì)方的 JavaScript 對(duì)象。但如果將這兩個(gè)頁(yè)面的 document.domain 值都設(shè)置為 "wrox.com"膳算,它們之間就可以通信了滥酥。
??瀏覽器對(duì) domain 屬性還有一個(gè)限制,即如果域名一開(kāi)始是“松散的”(loose)畦幢,那么不能將它再設(shè)置為“緊繃的”(tight)。
??換句話說(shuō)缆蝉,在將 document.domain 設(shè)置為 "wrox.com" 之后宇葱,就不能再將其設(shè)置回 "p2p.wrox.com",否則將會(huì)導(dǎo)致錯(cuò)誤刊头,如下面的例子所示黍瞧。

// 假設(shè)頁(yè)面來(lái)自于 p2p.wrox.com 域
document.domain = "wrox.com"; // 松散的(成功)
document.domain = "p2p.wrox.com"; // 緊繃的(出錯(cuò)!)

??所有瀏覽器中都存在這個(gè)限制原杂,但 IE8 是實(shí)現(xiàn)這一限制的最早的 IE 版本印颤。

3. 查找元素

??說(shuō)到最常見(jiàn)的 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>

var div = document.getElementById("myDiv"); // 取得<div>元素的引用

var div = document.getElementById("mydiv"); // 無(wú)效的 ID(在 IE7 及更早版本中可以)

??IE8 及較低版本不區(qū)分 ID 的大小寫悴了,因此 "myDiv" 和 "mydiv" 會(huì)被當(dāng)作相同的元素 ID。

??如果頁(yè)面中多個(gè)元素的 ID 值相同汗菜,getElementById() 只返回文檔中第一次出現(xiàn)的元素让禀。IE7 及較低版本還為此方法添加了一個(gè)有意思的“怪癖”:name 特性與給定 ID 匹配的表單元素(<input>、<textarea>陨界、<button>及<select>)也會(huì)被該方法返回巡揍。
??如果有哪個(gè)表單元素的 name 特性等于指定的 ID,而且該元素在文檔中位于帶有給定 ID 的元素前面菌瘪,那么 IE 就會(huì)返回那個(gè)表單元素腮敌。來(lái)看下面的例子。

<input type="text" name="myElement" value="Text field">
<div id="myElement">A div</div> 

document.getElementById("myElement") // IE7及更早版本返回 <input> 元素

??另一個(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)訪問(wèn) 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> 元素:

<img src="myimage.gif" name="myImage">

var images = document.getElementsByTagName("img"); 
var myImage = images.namedItem("myImage"); 

??在提供按索引訪問(wèn)項(xiàng)的基礎(chǔ)上卖子,HTMLCollection 還支持按名稱訪問(wèn)項(xiàng),這就為我們?nèi)〉脤?shí)際想要的元素提供了便利刑峡。
??而且洋闽,對(duì)命名的項(xiàng)也可以使用方括號(hào)語(yǔ)法來(lái)訪問(wèn),如下所示:

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)。

??雖然標(biāo)準(zhǔn)規(guī)定標(biāo)簽名需要區(qū)分大小寫纯衍,但為了最大限度地與既有 HTML 頁(yè)面兼容栋齿,傳給 getElementsByTagName() 的標(biāo)簽名是不需要區(qū)分大小寫的。
??但對(duì)于 XML 頁(yè)面而言(包括 XHTML)襟诸,getElementsByTagName() 方法就會(huì)區(qū)分大小寫瓦堵。

??第三個(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> 

??如上述例子所示,其中所有單選按鈕的 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 特性都相同)兄渺。

4. 特殊集合

??除了屬性和方法,document 對(duì)象還有一些特殊的集合叔壤。這些集合都是 HTMLCollection 對(duì)象口叙,為訪問(wèn)文檔常用的部分提供了快捷方式庐扫,包括:

  • 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ì)象訪問(wèn)到,而且,與 HTMLCollection 對(duì)象類似逮京,集合中的項(xiàng)也會(huì)隨著當(dāng)前文檔內(nèi)容的更新而更新。

5. DOM 一致性檢測(cè)

??由于 DOM 分為多個(gè)級(jí)別挟裂,也包含多個(gè)部分,因此檢測(cè)瀏覽器實(shí)現(xiàn)了 DOM 的哪些部分就十分必要了。
??document.implementation 屬性就是為此提供相應(yīng)信息和功能的對(duì)象份名,與瀏覽器對(duì) DOM 的實(shí)現(xiàn)直接對(duì)應(yīng)壶栋。
??DOM1 級(jí)只為 document.implementation 規(guī)定了一個(gè)方法,即 hasFeature()豌蟋。
??這個(gè)方法接受兩個(gè)參數(shù):要檢測(cè)的 DOM 功能的名稱及版本號(hào)施符。如果瀏覽器支持給定名稱和版本的功能,則該方法返回 true塘雳,如下面的例子所示:

var hasXmlDom = document.implementation.hasFeature("XML", "1.0"); 

??盡管使用 hasFeature() 確實(shí)方便太防,但也有缺點(diǎn)讳嘱。因?yàn)閷?shí)現(xiàn)者可以自行決定是否與 DOM 規(guī)范的不同部分保持一致嬉挡。
??事實(shí)上,要想讓 hasFearture() 方法針對(duì)所有值都返回 true 很容易,但返回 true 有時(shí)候也不意味著實(shí)現(xiàn)與規(guī)范一致。
??例如,Safari 2.x 及更早版本會(huì)在沒(méi)有完全實(shí)現(xiàn)某些 DOM 功能的情況下也返回 true。為此,我們建議多數(shù)情況下,在使用 DOM 的某些特殊的功能之前茬故,最好除了檢測(cè) hasFeature() 之外醉箕,還同時(shí)使用能力檢測(cè)松邪。

6. 文檔寫入

??有一個(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> 

??上述例子展示了在頁(yè)面加載過(guò)程中輸出當(dāng)前日期和時(shí)間的代碼。其中,日期被包含在一個(gè) <strong> 元素中,就像在 HTML 頁(yè)面中包含普通的文本一樣。這樣會(huì)創(chuàng)建一個(gè) DOM 元素钳降,而且可以在將來(lái)訪問(wèn)該元素澈蝙。
??通過(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)沒(méi)錯(cuò),但字符串 "</script>" 將被解釋為與外部的 </script> 標(biāo)簽匹配,結(jié)果文本 "); 將會(huì)出現(xiàn)在頁(yè)面中。為避免這個(gè)問(wèn)題,只需加入轉(zhuǎn)義字符\ 即可;解決方案如下:

<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> 

??在上述例子中,我們使用了 window.onload 事件處理程序,等到頁(yè)面完全加載之后延遲執(zhí)行函數(shù)。函數(shù)執(zhí)行之后憔披,字符串 "Hello world!" 會(huì)重寫整個(gè)頁(yè)面內(nèi)容。

??方法 open() 和 close() 分別用于打開(kāi)和關(guān)閉網(wǎng)頁(yè)的輸出流锈遥。如果是在頁(yè)面加載期間使用 write() 和 writeIn() 方法爬立,則不需要用到這兩個(gè)方法。

??嚴(yán)格型 XHTML 文檔不支持文檔寫入奕巍。對(duì)于那些按照 application/xml+xhtml 內(nèi)容類型提供的頁(yè)面吟策,這兩個(gè)方法也同樣無(wú)效。

1.3的止、Element 類型

??除了 Document 類型之外,Element 類型就要算是 Web 編程中最常用的類型了。
??Element 類型用于表現(xiàn) XML 或 HTML 元素匾委,提供了對(duì)元素簽名拖叙、子節(jié)點(diǎn)及特性的訪問(wèn)。Element 節(jié)點(diǎn)具有以下特征:

  • nodeType 的值為 1赂乐;
  • nodeName 的值為元素的標(biāo)簽名薯鳍;
  • nodeValue 的值為 null;
  • parentNode 可能是 Document 或 Element挨措;
  • 其子節(jié)點(diǎn)可能是 Element挖滤、Text、Comment运嗜、ProcessingInstruction壶辜、CDATASection 或 EntityReference。

??要訪問(wèn)元素的標(biāo)簽名担租,可以使用 nodeName 屬性砸民,也可以使用 tagName 屬性;這兩個(gè)屬性會(huì)返回相同的值(使用后者主要是為了清晰可見(jiàn))奋救。示例:

<div id="myDiv"></div> 

var div = document.getElementById("myDiv");

console.log(div.tagName); // "DIV"
console.log(div.tagName === div.nodeName); // true 

??這里的元素標(biāo)簽名是 div岭参,它擁有一個(gè)值為 "myDiv" 的 ID〕⑺遥可是演侯,div.tagName 實(shí)際上輸出的是 "DIV" 而非 "div"。
??在 HTML 中背亥,標(biāo)簽名始終都以全部大寫表示秒际;而在 XML(有時(shí)候也包括 XHTML)中,標(biāo)簽名則始終會(huì)與源代碼中的保持一致狡汉。假如你不確定自己的腳本將會(huì)在 HTML 還是 XML 文檔中執(zhí)行娄徊,最好是在比較之前將標(biāo)簽名轉(zhuǎn)換為相同的大小寫形式,示例:

if (element.tagName == "div"){ // 不能這樣比較盾戴,很容易出錯(cuò)寄锐!
    // 在此執(zhí)行某些操作
}
if (element.tagName.toLowerCase() == "div"){ // 這樣最好(適用于任何文檔)
    // 在此執(zhí)行某些操作
} 

??上述例子展示了圍繞 tagName 屬性的兩次比較操作。第一次比較容易出錯(cuò)尖啡,因?yàn)槠浯a在 HTML 文檔中不管用橄仆。第二次比較將標(biāo)簽名轉(zhuǎn)換成了全部小寫,是我們推薦的做法衅斩,因?yàn)檫@種做法適用于 HTML 文檔盆顾,也適用于 XML 文檔。

1. HTML 元素

??所有 HTML 元素都由 HTMLElement 類型表示畏梆,不是直接通過(guò)這個(gè)類型您宪,也是通過(guò)它的子類型來(lái)表示惫搏。HTMLElement 類型直接繼承自 Element 并添加了一些屬性。
??添加的這些屬性分別對(duì)應(yīng)于每個(gè) HTML 元素中都存在的下列標(biāo)準(zhǔn)特性蚕涤。

  • id筐赔,元素在文檔中的唯一標(biāo)識(shí)符。
  • title揖铜,有關(guān)元素的附加說(shuō)明信息茴丰,一般通過(guò)工具提示條顯示出來(lái)。
  • lang天吓,元素內(nèi)容的語(yǔ)言代碼贿肩,很少使用。
  • dir龄寞,語(yǔ)言的方向汰规,值為 "ltr"(left-to-right,從左至右)或 "rtl"(right-to-left物邑,從右至左)溜哮,也很少使用。
  • className色解,與元素的 class 特性對(duì)應(yīng)茂嗓,即為元素指定的 CSS 類。沒(méi)有將這個(gè)屬性命名為 class科阎,是因?yàn)?class 是 ECMAScript 的保留字述吸。

??上述這些屬性都可以用來(lái)取得或修改相應(yīng)的特性值。以下面的 HTML 的元素為例:

<div id="myDiv" class="bd" title="Body text" lang="en" dir="ltr"></div> 

// 元素中指定的所有信息锣笨,都可以通過(guò)下列 JavaScript 代碼取得
var div = document.getElementById("myDiv");

console.log(div.id); // "myDiv""
console.log(div.className); // "bd"
console.log(div.title); // "Body text"
console.log(div.lang); // "en"
console.log(div.dir); // "ltr" 

??當(dāng)然蝌矛,像下面這樣通過(guò)為每個(gè)屬性賦予新的值,也可以修改對(duì)應(yīng)的每個(gè)特性:

div.id = "someOtherId";
div.className = "ft";
div.title = "Some other text";
div.lang = "fr";
div.dir ="rtl"; 

??并不是對(duì)所有屬性的修改都會(huì)在頁(yè)面中直觀地表現(xiàn)出來(lái)错英。對(duì) id 或 lang 的修改對(duì)用戶而言是透明不可見(jiàn)的(假設(shè)沒(méi)有基于它們的值設(shè)置的 CSS 樣式)入撒,而對(duì) title 的修改則只會(huì)在鼠標(biāo)移動(dòng)到這個(gè)元素之上時(shí)才會(huì)顯示出來(lái)。對(duì) dir 的修改會(huì)在屬性被重寫的那一刻走趋,立即影響頁(yè)面中文本的左衅金、右對(duì)齊方式噪伊。
??修改 className 時(shí)簿煌,如果新類關(guān)聯(lián)了與此前不同的 CSS 樣式,那么就會(huì)立即應(yīng)用新的樣式鉴吹。
??前面提到過(guò)姨伟,所有 HTML 元素都是由 HTMLElement 或者其更具體的子類型來(lái)表示的。

2. 取得特性

??每個(gè)元素都有一或多個(gè)特性豆励,這些特性的用途是給出相應(yīng)元素或其內(nèi)容的附加信息夺荒。
??操作特性的 DOM 方法主要有三個(gè)瞒渠,分別是 getAttribute()、setAttribute() 和 removeAttribute()技扼。這三個(gè)方法可以針對(duì)任何特性使用伍玖,包括那些以 HTMLElement 類型屬性的形式定義的特性。示例:

<div id="myDiv" class="bd" title="Body text" lang="en" dir="ltr"></div> 

var div = document.getElementById("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"丽旅,后者只有在通過(guò)對(duì)象屬性訪問(wèn)特性時(shí)才用椰棘。
??如果給定名稱的特性不存在,getAttribute() 返回 null榄笙。

??通過(guò) getAttribute() 方法也可以取得自定義特性(即標(biāo)準(zhǔn) HTML 語(yǔ)言中沒(méi)有的特性)的值邪狞,以下面的元素為例:

<div id="myDiv" my_special_attribute="hello!"></div> 

var div = document.getElementById("myDiv");
var value = div.getAttribute("my_special_attribute");

??不過(guò),特性的名稱是不區(qū)分大小寫的茅撞,即"ID"和"id"代表的都是同一個(gè)特性帆卓。另外也要注意,根據(jù) HTML5 規(guī)范米丘,自定義特性應(yīng)該加上 data- 前綴以便驗(yàn)證鳞疲。

??任何元素的所有特性,也都可以通過(guò) DOM 元素本身的屬性來(lái)訪問(wèn)蠕蚜。當(dāng)然尚洽,HTMLElement 也會(huì)有 5 個(gè)屬性與相應(yīng)的特性一一對(duì)應(yīng)。不過(guò)靶累,只有公認(rèn)的(非自定義的)特性才會(huì)以屬性的形式添加到 DOM 對(duì)象中腺毫。以下面的元素為例:

<div id="myDiv" align="left" my_special_attribute="hello!"></div> 

??因?yàn)?id 和 align 在 HTML 中是 <div> 的公認(rèn)特性,因此該元素的 DOM 對(duì)象中也將存在對(duì)應(yīng)的屬性挣柬。不過(guò)潮酒,自定義特性 my_special_attribute 在 Safari、Opera邪蛔、Chrome 及 Firefox 中是不存在的急黎;但 IE 卻會(huì)為自定義特性也創(chuàng)建屬性,如下面的例子所示:

console.log(div.id); // "myDiv"
console.log(div.my_special_attribute); // undefined(IE 除外)
console.log(div.align); // "left" 

??有兩類特殊的特性侧到,它們雖然有對(duì)應(yīng)的屬性名勃教,但屬性的值與通過(guò) getAttribute() 返回的值并不相同。
??第一類特性就是 style匠抗,用于通過(guò) CSS 為元素指定樣式故源。在通過(guò) getAttribute() 訪問(wèn)時(shí),返回的 style 特性值中包含的是 CSS 文本汞贸,而通過(guò)屬性來(lái)訪問(wèn)它則會(huì)返回一個(gè)對(duì)象绳军。由于 style 屬性是用于以編程方式訪問(wèn)元素樣式的印机,因此并沒(méi)有直接映射到 style 特性。
??第二類與眾不同的特性是 onclick 這樣的事件處理程序门驾。當(dāng)在元素上使用時(shí)射赛,onclick 特性中包含的是 JavaScript 代碼,如果通過(guò) getAttribute() 訪問(wèn)奶是,則會(huì)返回相應(yīng)代碼的字符串咒劲。而在訪問(wèn) onclick 屬性時(shí),則會(huì)返回一個(gè) JavaScript 函數(shù)(如果未在元素中指定相應(yīng)特性诫隅,則返回 null)腐魂。這是因?yàn)?onclick 及其他事件處理程序?qū)傩员旧砭蛻?yīng)該被賦予函數(shù)值。
??由于存在這些差別逐纬,在通過(guò) JavaScript 以編程方式操作 DOM 時(shí)蛔屹,開(kāi)發(fā)人員經(jīng)常不使用 getAttribute(),而是只使用對(duì)象的屬性豁生。只有在取得自定義特性值的情況下兔毒,才會(huì)使用 getAttribute() 方法。

3. 設(shè)置特性

??與 getAttribute() 對(duì)應(yīng)的方法是 setAttribute()甸箱,這個(gè)方法接受兩個(gè)參數(shù):要設(shè)置的特性名和值育叁。
??如果特性已經(jīng)存在,setAttribute() 會(huì)以指定的值替換現(xiàn)有的值芍殖;如果特性不存在豪嗽,setAttribute() 則創(chuàng)建該屬性并設(shè)置相應(yīng)的值。示例:

div.setAttribute("id", "someOtherId");
div.setAttribute("class", "ft");
div.setAttribute("title", "Some other text");
div.setAttribute("lang", "fr");
div.setAttribute("dir", "rtl"); 

??通過(guò) setAttribute() 方法既可以操作 HTML 特性也可以操作自定義特性豌骏。通過(guò)這個(gè)方法設(shè)置的特性名會(huì)被統(tǒng)一轉(zhuǎn)換為小寫形式即 "ID" 最終會(huì)變成 "id" 龟梦。

??因?yàn)樗刑匦远际菍傩裕灾苯咏o屬性賦值可以設(shè)置特性的值窃躲,如下所示计贰。

div.id = "someOtherId";
div.align = "left"; 

??不過(guò),像下面這樣為 DOM 元素添加一個(gè)自定義的屬性蒂窒,該屬性不會(huì)自動(dòng)成為元素的特性躁倒。

div.mycolor = "red";
console.log(div.getAttribute("mycolor")); // null(IE 除外)

??上述例子添加了一個(gè)名為 mycolor 的屬性并將它的值設(shè)置為 "red"。在大多數(shù)瀏覽器中洒琢,這個(gè)屬性都不會(huì)自動(dòng)變成元素的特性秧秉,因此想通過(guò) getAttribute() 取得同名特性的值,結(jié)果會(huì)返回 null纬凤「U辏可是撩嚼,自定義屬性在 IE 中會(huì)被當(dāng)作元素的特性停士,反之亦然挖帘。

??要介紹的最后一個(gè)方法是 removeAttribute(),這個(gè)方法用于徹底刪除元素的特性恋技。調(diào)用這個(gè)方法不僅會(huì)清除特性的值拇舀,而且也會(huì)從元素中完全刪除特性,如下所示:

div.removeAttribute("class"); 

??這個(gè)方法并不常用蜻底,但在序列化 DOM 元素時(shí)骄崩,可以通過(guò)它來(lái)確切地指定要包含哪些特性。

4. 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);
  • setNamedItem(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; 

// 使用方括號(hào)語(yǔ)法通過(guò)特性名稱訪問(wèn)節(jié)點(diǎn)的簡(jiǎn)寫方式
var id = element.attributes["id"].nodeValue;

??也可以使用這種語(yǔ)法來(lái)設(shè)置特性的值,即先取得特性節(jié)點(diǎn)皮胡,然后再將其 nodeValue 設(shè)置為新值痴颊,如下所示。

element.attributes["id"].nodeValue = "someOtherId"; 

??調(diào)用 removeNamedItem() 方法與在元素上調(diào)用 removeAttribute() 方法的效果相同——直接刪除具有給定名稱的特性屡贺。
??下面的例子展示了兩個(gè)方法間唯一的區(qū)別蠢棱,即 removeNamedItem() 返回表示被刪除特性的 Attr 節(jié)點(diǎn)。

var oldAttr = element.attributes.removeNamedItem("id"); 

??最后甩栈,setNamedItem() 是一個(gè)很不常用的方法泻仙,通過(guò)這個(gè)方法可以為元素添加一個(gè)新特性,為此需要為它傳入一個(gè)特性節(jié)點(diǎn)量没,如下所示玉转。

element.attributes.setNamedItem(newAttr); 

??一般來(lái)說(shuō),由于前面介紹的 attributes 的方法不夠方便殴蹄,因此開(kāi)發(fā)人員更多的會(huì)使用 getAttribute()究抓、removeAttribute() 和 setAttribute() 方法猾担。

??不過(guò),如果想要遍歷元素的特性刺下,attributes 屬性倒是可以派上用場(chǎng)绑嘹。在需要將 DOM 結(jié)構(gòu)序列化為 XML 或 HTML 字符串時(shí),多數(shù)都會(huì)涉及遍歷元素特性橘茉。
??以下代碼展示了如何迭代元素的每一個(gè)特性工腋,然后將它們構(gòu)造成 name="value" name="value"這樣的字符串格式。

function outputAttributes(element){
    var pairs = new Array(),
        attrName,
        attrValue,
        i,
        len;
    for (i=0, len=element.attributes.length; i < len; i++){
        attrName = element.attributes[i].nodeName;
        attrValue = element.attributes[i].nodeValue;
        pairs.push(attrName + "=\"" + attrValue + "\"");
    }
    return pairs.join(" ");
} 

??上述函數(shù)使用了一個(gè)數(shù)組來(lái)保存名值對(duì)畅卓,最后再以空格為分隔符將它們拼接起來(lái)(這是序列化長(zhǎng)字符串時(shí)的一種常用技巧)擅腰。通過(guò) attributes.length 屬性,for 循環(huán)會(huì)遍歷每個(gè)特性翁潘,將特性的名稱和值輸出為字符串惕鼓。

??關(guān)于上述代碼的運(yùn)行結(jié)果,以下是兩點(diǎn)必要的說(shuō)明唐础。

  • 針對(duì) attributes 對(duì)象中的特性箱歧,不同瀏覽器返回的順序不同。這些特性在 XML 或 HTML 代碼中出現(xiàn)的先后順序一膨,不一定與它們出現(xiàn)在 attributes 對(duì)象中的順序一致呀邢。
  • IE7 及更早的版本會(huì)返回 HTML 元素中所有可能的特性,包括沒(méi)有指定的特性。換句話說(shuō),返回 100 多個(gè)特性的情況會(huì)很常見(jiàn)笔宿。

??針對(duì) IE7 及更早版本中存在的問(wèn)題膛锭,可以對(duì)上面的函數(shù)加以改進(jìn)八回,讓它只返回指定的特性。每個(gè)特性節(jié)點(diǎn)都有一個(gè)名為 specified 的屬性,這個(gè)屬性的值如果為 true,則意味著要么是在 HTML 中指定了相應(yīng)特性病毡,要么是通過(guò) setAttribute() 方法設(shè)置了該特性。
??在 IE 中屁柏,所有未設(shè)置過(guò)的特性的該屬性值都為 false啦膜,而在其他瀏覽器中根本不會(huì)為這類特性生成對(duì)應(yīng)的特性節(jié)點(diǎn)(因此,在這些瀏覽器中淌喻,任何特性節(jié)點(diǎn)的 specified 值始終為 true)僧家。改進(jìn)后的代碼如下所示。

function outputAttributes(element){
    var pairs = new Array(),
        attrName,
        attrValue,
        i,
        len;
    for (i=0, len=element.attributes.length; i < len; i++){
        attrName = element.attributes[i].nodeName;
        attrValue = element.attributes[i].nodeValue;
        if (element.attributes[i].specified) {
            pairs.push(attrName + "=\"" + attrValue + "\"");
        }
    }
    return pairs.join(" ");
} 

??這個(gè)經(jīng)過(guò)改進(jìn)的函數(shù)可以確保即使在 IE7 及更早的版本中裸删,也會(huì)只返回指定的特性八拱。

5. 創(chuàng)建元素

??使用 document.createElement() 方法可以創(chuàng)建新元素。這個(gè)方法只接受一個(gè)參數(shù),即要?jiǎng)?chuàng)建元素的標(biāo)簽名肌稻。
??這個(gè)標(biāo)簽名在 HTML 文檔中不區(qū)分大小寫清蚀,而在 XML(包括 XHTML)文檔中,則是區(qū)分大小寫的灯萍。例如轧铁,使用下面的代碼可以創(chuàng)建一個(gè)<div>元素每聪。

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

??在使用 createElement() 方法創(chuàng)建新元素的同時(shí)旦棉,也為新元素設(shè)置了 ownerDocuemnt 屬性。此時(shí)药薯,還可以操作元素的特性绑洛,為它添加更多子節(jié)點(diǎn),以及執(zhí)行其他操作童本。來(lái)看下面的例子真屯。

div.id = "myNewDiv";
div.className = "box"; 

??在新元素上設(shè)置這些特性只是給它們賦予了相應(yīng)的信息。由于新元素尚未被添加到文檔樹(shù)中穷娱,因此設(shè)置這些特性不會(huì)影響瀏覽器的顯示绑蔫。
??要把新元素添加到文檔樹(shù),可以使用 appendChild()泵额、insertBefore() 或 replaceChild() 方法配深。
??下面的代碼會(huì)把新創(chuàng)建的元素添加到文檔的 <body> 元素中。

document.body.appendChild(div); 

??一旦將元素添加到文檔樹(shù)中嫁盲,瀏覽器就會(huì)立即呈現(xiàn)該元素篓叶。此后,對(duì)這個(gè)元素所作的任何修改都會(huì)實(shí)時(shí)反映在瀏覽器中羞秤。

??在 IE 中可以以另一種方式使用 createElement()缸托,即為這個(gè)方法傳入完整的元素標(biāo)簽,也可以包含屬性瘾蛋,如下面的例子所示俐镐。

var div = document.createElement("<div id=\"myNewDiv\" class=\"box\"></div >");

??這種方式有助于避開(kāi)在 IE7 及更早版本中動(dòng)態(tài)創(chuàng)建元素的某些問(wèn)題。下面是已知的一些這類問(wèn)題哺哼。

  • 不能設(shè)置動(dòng)態(tài)創(chuàng)建的 <iframe> 元素的 name 特性京革。
  • 不能通過(guò)表單的 reset() 方法重設(shè)動(dòng)態(tài)創(chuàng)建的 <input> 元素。
  • 動(dòng)態(tài)創(chuàng)建的 type 特性值為 "reset" 的 <buttou> 元素重設(shè)不了表單幸斥。
  • 動(dòng)態(tài)創(chuàng)建的一批 name 相同的單選按鈕彼此毫無(wú)關(guān)系匹摇。name 值相同的一組單選按鈕本來(lái)應(yīng)該用于表示同一選項(xiàng)的不同值,但動(dòng)態(tài)創(chuàng)建的一批這種單選按鈕之間卻沒(méi)有這種關(guān)系甲葬。

??上述所有問(wèn)題都可以通過(guò)在createElement() 中指定完整的 HTML 標(biāo)簽來(lái)解決廊勃,如下面的例子所示。

if (client.browser.ie && client.browser.ie <=7){
    // 創(chuàng)建一個(gè)帶 name 特性的 iframe 元素
    var iframe = document.createElement("<iframe name=\"myframe\"></iframe>");
    // 創(chuàng)建 input 元素
    var input = document.createElement("<input type=\"checkbox\">");
    // 創(chuàng)建 button 元素
    var button = document.createElement("<button type=\"reset\"></button>");
    // 創(chuàng)建單選按鈕
    var radio1 = document.createElement("<input type=\"radio\" name=\"choice\" "+"value=\"1\">");
    var radio2 = document.createElement("<input type=\"radio\" name=\"choice\" "+"value=\"2\">");
} 

??與使用 createElement() 的慣常方式一樣,這樣的用法也會(huì)返回一個(gè) DOM 元素的引用坡垫∷蟛樱可以將這個(gè)引用添加到文檔中,也可以對(duì)其加以增強(qiáng)冰悠。但是堡妒,由于這樣的用法要求使用瀏覽器檢測(cè),因此我們建議只在需要避開(kāi) IE 及更早版本中上述某個(gè)問(wèn)題的情況下使用溉卓。其他瀏覽器都不支持這種用法皮迟。

6. 元素的子節(jié)點(diǎn)

??元素可以有任意數(shù)目的子節(jié)點(diǎn)和后代節(jié)點(diǎn),因?yàn)樵乜梢允瞧渌氐淖庸?jié)點(diǎn)桑寨。元素的 childNodes 屬性中包含了它的所有子節(jié)點(diǎn)伏尼,這些子節(jié)點(diǎn)有可能是元素、文本節(jié)點(diǎn)尉尾、注釋或處理指令爆阶。
??不同瀏覽器在看待這些節(jié)點(diǎn)方面存在顯著的不同,以下面的代碼為例沙咏。

<ul id="myList">
    <li>Item 1</li>
    <li>Item 2</li>
    <li>Item 3</li>
</ul> 

??如果是 IE 來(lái)解析這些代碼辨图,那么 <ul> 元素會(huì)有 3 個(gè)子節(jié)點(diǎn),分別是 3 個(gè) <li> 元素肢藐。
??但如果是在其他瀏覽器中故河,<ul> 元素都會(huì)有 7 個(gè)元素,包括 3 個(gè) <li> 元素和 4 個(gè)文本節(jié)點(diǎn)(表示 <li> 元素之間的空白符)窖壕。
??如果像下面這樣將元素間的空白符刪除忧勿,那么所有瀏覽器都會(huì)返回相同數(shù)目的子節(jié)點(diǎn)。

<ul id="myList"><li>Item 1</li><li>Item 2</li><li>Item 3</li></ul> 

??對(duì)于上述代碼瞻讽,<ul> 元素在任何瀏覽器中都會(huì)包含 3 個(gè)子節(jié)點(diǎn)鸳吸。如果需要通過(guò) childNodes 屬性遍歷子節(jié)點(diǎn),那么一定不要忘記瀏覽器間的這一差別速勇。這意味著在執(zhí)行某項(xiàng)操作以前晌砾,通常都要先檢查一下 nodeTpye 屬性,如下面的例子所示烦磁。

for (var i=0, len=element.childNodes.length; i < len; i++){
    if (element.childNodes[i].nodeType == 1){
        // 執(zhí)行某些操作
    }
} 

??上述例子會(huì)循環(huán)遍歷特定元素的每一個(gè)子節(jié)點(diǎn)养匈,然后只在子節(jié)點(diǎn)的 nodeType 等于 1(表示是元素節(jié)點(diǎn))的情況下,才會(huì)執(zhí)行某些操作都伪。
??如果想通過(guò)某個(gè)特定的標(biāo)簽名取得子節(jié)點(diǎn)或后代節(jié)點(diǎn)該怎么辦呢呕乎?實(shí)際上,元素也支持 getElementsByTagName() 方法陨晶。在通過(guò)元素調(diào)用這個(gè)方法時(shí)猬仁,除了搜索起點(diǎn)是當(dāng)前元素之外,其他方面都跟通過(guò) document 調(diào)用這個(gè)方法相同,因此結(jié)果只會(huì)返回當(dāng)前元素的后代湿刽。
??例如的烁,要想取得前面 <ul> 元素中包含的所有 <li> 元素,可以使用下列代碼诈闺。

var ul = document.getElementById("myList");
var items = ul.getElementsByTagName("li");

??要注意的是渴庆,這里 <ul> 的后代中只包含直接子元素。不過(guò)雅镊,如果它包含更多層次的后代元素襟雷,那么各個(gè)層次中包含的 <li> 元素也都會(huì)返回。

1.4漓穿、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胧砰;
  • 不支持(沒(méi)有)子節(jié)點(diǎn)鳍鸵。

??可以通過(guò) nodeValue 屬性或 data 屬性訪問(wèn) Text 節(jié)點(diǎn)中包含的文本,這兩個(gè)屬性中包含的值相同尉间。對(duì) nodeValue 的修改也會(huì)通過(guò) data 反映出來(lái)偿乖,反之亦然。使用下列方法可以操作節(jié)點(diǎn)中的文本哲嘲。

  • appendData(text):將 text 添加到節(jié)點(diǎn)的末尾贪薪。
  • deleteData(offset, count):從 offset 指定的位置開(kāi)始刪除 count 個(gè)字符。
  • insertData(offset, text):在 offset 指定的位置插入 text眠副。
  • replaceData(offset, count, text):用 text 替換從 offset 指定的位置開(kāi)始到 offset + count 為止處的文本画切。
  • splitText(offset):從 offset 指定的位置將當(dāng)前文本節(jié)點(diǎn)分成兩個(gè)文本節(jié)點(diǎn)。
  • substringData(offset, count):提取從 offset 指定的位置開(kāi)始到offset + count 為止處的字符串囱怕。

??除了這些方法之外霍弹,文本節(jié)點(diǎn)還有一個(gè) length 屬性,保存著節(jié)點(diǎn)中字符的數(shù)目娃弓。而且典格,nodeValue.length 和 data.length 中也保存著同樣的值。
??在默認(rèn)情況下台丛,每個(gè)可以包含內(nèi)容的元素最多只能有一個(gè)文本節(jié)點(diǎn)耍缴,而且必須確實(shí)有內(nèi)容存在。來(lái)看幾個(gè)例子。

<!-- 沒(méi)有內(nèi)容私恬,也就沒(méi)有文本節(jié)點(diǎn) -->
<div></div>

<!-- 有空格债沮,因而有一個(gè)文本節(jié)點(diǎn) -->
<div> </div>

<!-- 有內(nèi)容,因而有一個(gè)文本節(jié)點(diǎn) -->
<div>Hello World!</div> 

??上面代碼給出的第一個(gè) <div> 元素沒(méi)有內(nèi)容本鸣,因此也就不存在文本節(jié)點(diǎn)疫衩。

??開(kāi)始與結(jié)束標(biāo)簽之間只要存在內(nèi)容,就會(huì)創(chuàng)建一個(gè)文本節(jié)點(diǎn)荣德。因此闷煤,第二個(gè) <div> 元素中雖然只包含一個(gè)空格,但仍然有一個(gè)文本子節(jié)點(diǎn)涮瞻;文本節(jié)點(diǎn)的 nodeValue 值是一個(gè)空格鲤拿。

??第三個(gè) <div> 也有一個(gè)文本節(jié)點(diǎn),其 nodeValue 的值為"Hello World!"署咽〗辏可以使用以下代碼來(lái)訪問(wèn)這些文本子節(jié)點(diǎn)。

var textNode = div.firstChild; // 或者 div.childNodes[0] 

??在取得了文本節(jié)點(diǎn)的引用后宁否,就可以像下面這樣來(lái)修改它了窒升。

div.firstChild.nodeValue = "Some other message"; 

??如果這個(gè)文本節(jié)點(diǎn)當(dāng)前存在于文檔樹(shù)中,那么修改文本節(jié)點(diǎn)的結(jié)果就會(huì)立即得到反映慕匠。
??另外饱须,在修改文本節(jié)點(diǎn)時(shí)還要注意,此時(shí)的字符串會(huì)經(jīng)過(guò) HTML(或 XML台谊,取決于文檔類型)編碼蓉媳。換句話說(shuō),小于號(hào)锅铅、大于號(hào)或引號(hào)都會(huì)像下面的例子一樣被轉(zhuǎn)義酪呻。

// 輸出結(jié)果是"Some &lt;strong&gt;other&lt;/strong&gt; message"
div.firstChild.nodeValue = "Some <strong>other</strong> message"; 

??應(yīng)該說(shuō),這是在向 DOM 文檔中插入文本之前狠角,先對(duì)其進(jìn)行 HTML 編碼的一種有效方式号杠。

1. 創(chuàng)建文本節(jié)點(diǎn)

??可以使用 document.createTextNode() 創(chuàng)建新文本節(jié)點(diǎn),這個(gè)方法接受一個(gè)參數(shù)——要插入節(jié)點(diǎn)中的文本丰歌。
??與設(shè)置已有文本節(jié)點(diǎn)的值一樣姨蟋,作為參數(shù)的文本也將按照 HTML 或 XML 的格式進(jìn)行編碼。

var textNode = document.createTextNode("<strong>Hello</strong> world!");

??在創(chuàng)建新文本節(jié)點(diǎn)的同時(shí)立帖,也會(huì)為其設(shè)置 ownerDocument 屬性眼溶。不過(guò),除非把新節(jié)點(diǎn)添加到文檔樹(shù)中已經(jīng)存在的節(jié)點(diǎn)中晓勇,否則我們不會(huì)在瀏覽器窗口中看到新節(jié)點(diǎn)堂飞。
??下面的代碼會(huì)創(chuàng)建一個(gè) <div> 元素并向其中添加一條消息灌旧。

var element = document.createElement("div");
element.className = "message";

var textNode = document.createTextNode("Hello world!");
element.appendChild(textNode);

document.body.appendChild(element); 

??上述例子創(chuàng)建了一個(gè)新 <div> 元素并為它指定了值、為 "message" 的 class 特性绰筛。然后枢泰,又創(chuàng)建了一個(gè)文本節(jié)點(diǎn),并將其添加到前面創(chuàng)建的元素中铝噩。最后一步衡蚂,就是將這個(gè)元素添加到了文檔的 <body> 元素中,這樣就可以在瀏覽器中看到新創(chuàng)建的元素和文本節(jié)點(diǎn)了骏庸。

??一般情況下毛甲,每個(gè)元素只有一個(gè)文本子節(jié)點(diǎn)。不過(guò)具被,在某些情況下也可能包含多個(gè)文本子節(jié)點(diǎn)玻募,如下面的例子所示。

var element = document.createElement("div");
element.className = "message";

var textNode = document.createTextNode("Hello world!");
element.appendChild(textNode);

var anotherTextNode = document.createTextNode("Yippee!");
element.appendChild(anotherTextNode);

document.body.appendChild(element); 

??如果兩個(gè)文本節(jié)點(diǎn)是相鄰的同胞節(jié)點(diǎn)一姿,那么這兩個(gè)節(jié)點(diǎn)中的文本就會(huì)連起來(lái)顯示七咧,中間不會(huì)有空格。

2. 規(guī)范化文本節(jié)點(diǎn)

??DOM 文檔中存在相鄰的同胞文本節(jié)點(diǎn)很容易導(dǎo)致混亂啸蜜,因?yàn)榉植磺迥膫€(gè)文本節(jié)點(diǎn)表示哪個(gè)字符串坑雅。
??另外辈挂,DOM 文檔中出現(xiàn)相鄰文本節(jié)點(diǎn)的情況也不在少數(shù)衬横,于是就催生了一個(gè)能夠?qū)⑾噜徫谋竟?jié)點(diǎn)合并的方法。
??這個(gè)方法是由 Node 類型定義的(因而在所有節(jié)點(diǎn)類型中都存在)终蒂,名叫 normalize()蜂林。如果在一個(gè)包含兩個(gè)或多個(gè)文本節(jié)點(diǎn)的父元素上調(diào)用 normalize() 方法,則會(huì)將所有文本節(jié)點(diǎn)合并成一個(gè)節(jié)點(diǎn)拇泣,結(jié)果節(jié)點(diǎn)的 nodeValue 等于將合并前每個(gè)文本節(jié)點(diǎn)的 nodeValue 值拼接起來(lái)的值噪叙。示例:

var element = document.createElement("div");
element.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ì)作為執(zhí)行 DOM 操作的結(jié)果出現(xiàn)霉翔。

??在某些情況下睁蕾,執(zhí)行 normalize() 方法會(huì)導(dǎo)致 IE6 崩潰。不過(guò)债朵,在 IE6 后來(lái)的補(bǔ)丁中子眶,可能已經(jīng)修復(fù)了這個(gè)問(wèn)題(未經(jīng)證實(shí))。IE7 及更高版本中不存在這個(gè)問(wèn)題序芦。

3. 分割文本節(jié)點(diǎn)

??Text 類型提供了一個(gè)作用與 normalize() 相反的方法:splitText()臭杰。這個(gè)方法會(huì)將一個(gè)文本節(jié)點(diǎn)分成兩個(gè)文本節(jié)點(diǎn),即按照指定的位置分割 nodeValue 值谚中。原來(lái)的文本節(jié)點(diǎn)將包含從開(kāi)始到指定位置之前的內(nèi)容渴杆,新文本節(jié)點(diǎn)將包含剩下的文本寥枝。
??這個(gè)方法會(huì)返回一個(gè)新文本節(jié)點(diǎn),該節(jié)點(diǎn)與原節(jié)點(diǎn)的 parentNode 相同磁奖。來(lái)看下面的例子囊拜。

var element = document.createElement("div");
element.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); // 2

??在上述例子中,包含"Hello world!"的文本節(jié)點(diǎn)被分割為兩個(gè)文本節(jié)點(diǎn)比搭,從位置 5 開(kāi)始艾疟。
??位置 5是"Hello"和"world!"之間的空格,因此原來(lái)的文本節(jié)點(diǎn)將包含字符串"Hello"敢辩,而新文本節(jié)點(diǎn)將包含文本"world!"(包含空格)蔽莱。

??分割文本節(jié)點(diǎn)是從文本節(jié)點(diǎn)中提取數(shù)據(jù)的一種常用 DOM 解析技術(shù)。

1.5戚长、Comment 類型

??注釋在 DOM 中是通過(guò) Comment 類型來(lái)表示的盗冷。Comment 節(jié)點(diǎn)具有下列特征:

  • nodeType 的值為 8;
  • nodeName 的值為"#comment"同廉;
  • nodeValue 的值是注釋的內(nèi)容仪糖;
  • parentNode 可能是 Document 或 Element;
  • 不支持(沒(méi)有)子節(jié)點(diǎn)迫肖。

??Comment 類型與 Text 類型繼承自相同的基類锅劝,因此它擁有除 splitText() 之外的所有字符串操作方法。

??與 Text 類型相似蟆湖,也可以通過(guò) nodeValue 或 data 屬性來(lái)取得注釋的內(nèi)容故爵。

??注釋節(jié)點(diǎn)可以通過(guò)其父節(jié)點(diǎn)來(lái)訪問(wèn),示例:

<div id="myDiv"><!--A comment --></div>

??在此隅津,注釋節(jié)點(diǎn)是 <div> 元素的一個(gè)子節(jié)點(diǎn)诬垂,因此可以通過(guò)下面的代碼來(lái)訪問(wèn)它。

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 "); 

??顯然充蓝,開(kāi)發(fā)人員很少會(huì)創(chuàng)建和訪問(wèn)注釋節(jié)點(diǎn)隧枫,因?yàn)樽⑨尮?jié)點(diǎn)對(duì)算法鮮有影響。
??此外谓苟,瀏覽器也不會(huì)識(shí)別位于 </html> 標(biāo)簽后面的注釋官脓。如果要訪問(wèn)注釋節(jié)點(diǎn),一定要保證它們是 <html> 元素的后代(即位于 <html> 和 </html> 之間)娜谊。

1.6确买、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艺普;
  • 不支持(沒(méi)有)子節(jié)點(diǎn)簸州。

??CDATA 區(qū)域只會(huì)出現(xiàn)在 XML 文檔中,因此多數(shù)瀏覽器都會(huì)把 CDATA 區(qū)域錯(cuò)誤地解析為 Comment 或 Element歧譬。示例:

<div id="myDiv"><![CDATA[This is some content.]]></div>

??上述例子中的 <div> 元素應(yīng)該包含一個(gè) CDATASection 節(jié)點(diǎn)岸浑。可是瑰步,四大主流瀏覽器無(wú)一能夠這樣解析它矢洲。即使對(duì)于有效的 XHTML 頁(yè)面,瀏覽器也沒(méi)有正確地支持嵌入的 CDATA 區(qū)域缩焦。
??在真正的 XML 文檔中读虏,可以使用 document.createCDataSection() 來(lái)創(chuàng)建 CDATA 區(qū)域,只需為其傳入節(jié)點(diǎn)的內(nèi)容即可袁滥。

1.7盖桥、DocumentType 類型

??DocumentType 類型在 Web 瀏覽器中并不常用。DocumentType 包含著與文檔的 doctype 有關(guān)的所有信息题翻,它具有下列特征:

  • nodeType 的值為 10揩徊;
  • nodeName 的值為 doctype 的名稱;
  • nodeValue 的值為 null藐握;
  • parentNode 是 Document靴拱;
  • 不支持(沒(méi)有)子節(jié)點(diǎn)。

??在 DOM1 級(jí)中猾普,DocumentType 對(duì)象不能動(dòng)態(tài)創(chuàng)建,而只能通過(guò)解析文檔代碼的方式來(lái)創(chuàng)建本谜。支持它的瀏覽器會(huì)把 DocumentType 對(duì)象保存在 document.doctype 中 初家。
??DOM1 級(jí)描述了 DocumentType 對(duì)象的 3 個(gè)屬性:name、entities 和 notations乌助。
??其中溜在,name 表示文檔類型的名稱;entities 是由文檔類型描述的實(shí)體的 NamedNodeMap 對(duì)象他托;notations 是由文檔類型描述的符號(hào)的 NamedNodeMap 對(duì)象掖肋。
??通常,瀏覽器中的文檔使用的都是 HTML 或 XHTML 文檔類型赏参,因而 entities 和 notations 都是空列表(列表中的項(xiàng)來(lái)自行內(nèi)文檔類型聲明)志笼。
??但不管怎樣沿盅,只有 name 屬性是有用的。這個(gè)屬性中保存的是文檔類型的名稱纫溃,也就是出現(xiàn)在<!DOCTYPE 之后的文本腰涧。
??以下面嚴(yán)格型 HTML4.01 的文檔類型聲明為例:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">

??DocumentType 的 name 屬性中保存的就是"HTML":

console.log(document.doctype.name); // "HTML"

??IE 及更早版本不支持 DocumentType,因此 document.doctype 的值始終都等于 null紊浩。
??可是窖铡,這些瀏覽器會(huì)把文檔類型聲明錯(cuò)誤地解釋為注釋,并且為它創(chuàng)建一個(gè)注釋節(jié)點(diǎn)坊谁。IE9 會(huì) 給 document.doctype 賦正確的對(duì)象费彼,但仍然不支持訪問(wèn) DocumentType 類型。

1.8口芍、DocumentFragment 類型

??在所有節(jié)點(diǎn)類型中敌买,只有 DocumentFragment 在文檔中沒(méi)有對(duì)應(yīng)的標(biāo)記。
??DOM 規(guī)定文檔片段(document fragment)是一種“輕量級(jí)”的文檔阶界,可以包含和控制節(jié)點(diǎn)虹钮,但不會(huì)像完整的文檔那樣占用額外的資源。

??DocumentFragment 節(jié)點(diǎn)具有下列特征:

  • nodeType 的值為 11膘融;
  • nodeName 的值為"#document-fragment"芙粱;
  • nodeValue 的值為 null;
  • parentNode 的值為 null氧映;
  • 子節(jié)點(diǎn)可以是 Element春畔、ProcessingInstruction、Comment岛都、Text律姨、CDATASection 或 EntityReference影锈。

??雖然不能把文檔片段直接添加到文檔中泼橘,但可以將它作為一個(gè)“倉(cāng)庫(kù)”來(lái)使用,即可以在里面保存將來(lái)可能會(huì)添加到文檔中的節(jié)點(diǎn)官帘。
??要?jiǎng)?chuàng)建文檔片段烫堤,可以使用 document.createDocumentFragment() 方法荣赶,如下所示:

var fragment = document.createDocumentFragment(); 

??文檔片段繼承了 Node 的所有方法,通常用于執(zhí)行那些針對(duì)文檔的 DOM 操作鸽斟。
??如果將文檔中的節(jié)點(diǎn)添加到文檔片段中拔创,就會(huì)從文檔樹(shù)中移除該節(jié)點(diǎn),也不會(huì)從瀏覽器中再看到該節(jié)點(diǎn)富蓄。
??添加到文檔片段中的新節(jié)點(diǎn)同樣也不屬于文檔樹(shù)剩燥。可以通過(guò) appendChild() 或 insertBefore() 將文檔片段中內(nèi)容添加到文檔中立倍。
??在將文檔片段作為參數(shù)傳遞給這兩個(gè)方法時(shí)灭红,實(shí)際上只會(huì)將文檔片段的所有子節(jié)點(diǎn)添加到相應(yīng)位置上侣滩;文檔片段本身永遠(yuǎn)不會(huì)成為文檔樹(shù)的一部分。來(lái)看下面的 HTML 示例代碼:

<ul id="myList"></ul>

??假設(shè)我們想為這個(gè) <ul> 元素添加 3 個(gè)列表項(xiàng)比伏。如果逐個(gè)地添加列表項(xiàng)胜卤,將會(huì)導(dǎo)致瀏覽器反復(fù)渲染(呈現(xiàn))新信息。為避免這個(gè)問(wèn)題赁项,可以像下面這樣使用一個(gè)文檔片段來(lái)保存創(chuàng)建的列表項(xiàng)葛躏,然后再一次性將它們添加到文檔中。

var fragment = document.createDocumentFragment();
var ul = document.getElementById("myList");
var li = null;
for (var i=0; i < 3; i++){
    li = document.createElement("li");
    li.appendChild(document.createTextNode("Item " + (i+1)));
    fragment.appendChild(li);
}
ul.appendChild(fragment);

??在上述例子中悠菜,我們先創(chuàng)建一個(gè)文檔片段并取得了對(duì) <ul> 元素的引用舰攒。然后,通過(guò) for 循環(huán)創(chuàng)建 3 個(gè)列表項(xiàng)悔醋,并通過(guò)文本表示它們的順序摩窃。
??為此,需要分別創(chuàng)建 <li> 元素芬骄、創(chuàng)建文本節(jié)點(diǎn)猾愿,再把文本節(jié)點(diǎn)添加到 <li> 元素。
??接著使用 appendChild() 將 <li> 元素添加到文檔片段中账阻。循環(huán)結(jié)束后蒂秘,再調(diào)用 appendChild() 并傳入文檔片段,將所有列表項(xiàng)添加到 <ul> 元素中淘太。此時(shí)姻僧,文檔片段的所有子節(jié)點(diǎn)都被刪除并轉(zhuǎn)移到了 <ul> 元素中。

1.9蒲牧、Attr 類型

??元素的特性在 DOM 中以 Attr 類型來(lái)表示撇贺。在所有瀏覽器中(包括 IE8),都可以訪問(wèn) Attr 類型的構(gòu)造函數(shù)和原型冰抢。
??從技術(shù)角度講松嘶,特性就是存在于元素的 attributes 屬性中的節(jié)點(diǎn)。特性節(jié)點(diǎn)具有下列特征:

  • nodeType 的值為 2晒屎;
  • nodeName 的值是特性的名稱喘蟆;
  • nodeValue 的值是特性的值;
  • parentNode 的值為 null鼓鲁;
  • 在 HTML 中不支持(沒(méi)有)子節(jié)點(diǎn);
  • 在 XML 中子節(jié)點(diǎn)可以是 Text 或 EntityReference港谊。

??盡管它們也是節(jié)點(diǎn)骇吭,但特性卻不被認(rèn)為是 DOM 文檔樹(shù)的一部分。開(kāi)發(fā)人員最常使用的是 getAttribute()歧寺、setAttribute() 和 remveAttribute() 方法燥狰,很少直接引用特性節(jié)點(diǎn)棘脐。
??Attr 對(duì)象有 3 個(gè)屬性:name、value 和 specified龙致。其中蛀缝,name 是特性名稱(與 nodeName 的值相同),value 是特性的值(與 nodeValue 的值相同)目代,而 specified 是一個(gè)布爾值屈梁,用以區(qū)別特性是在代碼中指定的,還是默認(rèn)的榛了。
??使用 document.createAttribute() 并傳入特性的名稱可以創(chuàng)建新的特性節(jié)點(diǎn)在讶。例如,要為元素添加 align 特性霜大,可以使用下列代碼:

var attr = document.createAttribute("align");
attr.value = "left";
element.setAttributeNode(attr);
console.log(element.attributes["align"].value); // "left"
console.log(element.getAttributeNode("align").value); // "left"
console.log(element.getAttribute("align")); // "left"

??上述例子創(chuàng)建了一個(gè)新的特性節(jié)點(diǎn)构哺。由于在調(diào)用 createAttribute() 時(shí)已經(jīng)為 name 屬性賦了值,所以后面就不必給它賦值了战坤。
??之后曙强,又把 value 屬性的值設(shè)置為 "left"。為了將新創(chuàng)建的特性添加到元素中途茫,必須使用元素的 setAttributeNode() 方法碟嘴。
??添加特性之后,可以通過(guò)下列任何方式訪問(wèn)該特性:attributes 屬性慈省、getAttributeNode() 方法以及 getAttribute() 方法臀防。
??其中,attributes 和 getAttributeNode() 都會(huì)返回對(duì)應(yīng)特性的 Attr 節(jié)點(diǎn)边败,而 getAttribute() 則只返回特性的值袱衷。

??我們并不建議直接訪問(wèn)特性節(jié)點(diǎn)。實(shí)際上笑窜,使用 getAttribute()致燥、setAttribute() 和 removeAttribute() 方法遠(yuǎn)比操作特性節(jié)點(diǎn)更為方便。

2排截、DOM 操作技術(shù)

??很多時(shí)候嫌蚤,DOM 操作都比較簡(jiǎn)單,因此用 JavaScript 生成那些通常原本是用 HTML 代碼生成的內(nèi)容并不麻煩断傲。
??不過(guò)脱吱,也有一些時(shí)候,操作 DOM 并不像表面上看起來(lái)那么簡(jiǎn)單认罩。由于瀏覽器中充斥著隱藏的陷阱和不兼容問(wèn)題箱蝠,用 JavaScript 代碼處理 DOM 的某些部分要比處理其他部分更復(fù)雜一些。

2.1、動(dòng)態(tài)腳本

??使用 <script> 元素可以向頁(yè)面中插入 JavaScript 代碼宦搬,一種方式是通過(guò)其 src 特性包含外部文件牙瓢,另一種方式就是用這個(gè)元素本身來(lái)包含代碼。
??而這一節(jié)要討論的動(dòng)態(tài)腳本间校,指的是在頁(yè)面加載時(shí)不存在矾克,但將來(lái)的某一時(shí)刻通過(guò)修改 DOM 動(dòng)態(tài)添加的腳本。
??和操作 HTML 元素一樣憔足,創(chuàng)建動(dòng)態(tài)腳本也有兩種方式:插入外部文件和直接插入 JavaScript 代碼胁附。
??動(dòng)態(tài)加載的外部 JavaScript 文件能夠立即運(yùn)行,比如下面的 <script> 元素:

<script type="text/javascript" src="client.js"></script> 

??這個(gè) <script> 元素包含了客戶端檢測(cè)腳本四瘫。而創(chuàng)建這個(gè)節(jié)點(diǎn)的 DOM 代碼如下所示:

var script = document.createElement("script");
script.type = "text/javascript"; 
script.src = "client.js";
document.body.appendChild(script); 

??顯然汉嗽,這里的 DOM 代碼如實(shí)反映了相應(yīng)的 HTML 代碼。不過(guò)找蜜,在執(zhí)行最后一行代碼把 <script> 元素添加到頁(yè)面中之前饼暑,是不會(huì)下載外部文件的。也可以把這個(gè)元素添加到 <head> 元素中洗做,效果相同弓叛。整個(gè)過(guò)程可以使用下面的函數(shù)來(lái)封裝:

function loadScript(url){
    var script = document.createElement("script");
    script.type = "text/javascript";
    script.src = url;
    document.body.appendChild(script);
} 

??然后,就可以通過(guò)調(diào)用這個(gè)函數(shù)來(lái)加載外部的 JavaScript 文件了:

loadScript("client.js"); 

??加載完成后诚纸,就可以在頁(yè)面中的其他地方使用這個(gè)腳本了撰筷。
??問(wèn)題只有一個(gè):怎么知道腳本加載完成呢?遺憾的是畦徘,并沒(méi)有什么標(biāo)準(zhǔn)方式來(lái)探知這一點(diǎn)毕籽。不過(guò),與此相關(guān)的一些事件倒是可以派上用場(chǎng)井辆,但要取決于所用的瀏覽器关筒。

??另一種指定 JavaScript 代碼的方式是行內(nèi)方式,如下面的例子所示:

<script type="text/javascript">
    function sayHi(){
        console.log("hi");
    }
</script> 

??從邏輯上講杯缺,下面的 DOM 代碼是有效的:

var script = document.createElement("script");
script.type = "text/javascript";
script.appendChild(document.createTextNode("function sayHi(){console.log('hi');}"));
document.body.appendChild(script);

??在 Firefox蒸播、Safari、Chrome 和 Opera 中萍肆,這些 DOM 代碼可以正常運(yùn)行袍榆。
??但在 IE 中,則會(huì)導(dǎo)致錯(cuò)誤塘揣。IE 將 <script> 視為一個(gè)特殊的元素包雀,不允許 DOM 訪問(wèn)其子節(jié)點(diǎn)。不過(guò)亲铡,可以使用 <script> 元素的
text 屬性來(lái)指定 JavaScript 代碼馏艾,像下面的例子這樣:

var script = document.createElement("script");
script.type = "text/javascript";
script.text = "function sayHi(){console.log('hi');}";
document.body.appendChild(script); 

??經(jīng)過(guò)這樣修改之后的代碼可以在 IE劳曹、Firefox奴愉、Opera 和 Safari 3 及之后版本中運(yùn)行琅摩。
??Safari 3.0 之前的版本雖然不能正確地支持 text 屬性,但卻允許使用文本節(jié)點(diǎn)技術(shù)來(lái)指定代碼锭硼。如果需要兼容早期版本的 Safari房资,可以使用下列代碼:

var script = document.createElement("script");
script.type = "text/javascript";
var code = "function sayHi(){console.log('hi');}"; 
try {
    script.appendChild(document.createTextNode("code"));
} catch (ex){
    script.text = "code";
}
document.body.appendChild(script); 

??這里,首先嘗試標(biāo)準(zhǔn)的 DOM 文本節(jié)點(diǎn)方法檀头,因?yàn)槌?IE(在 IE 中會(huì)導(dǎo)致拋出錯(cuò)誤)轰异,所有瀏覽器都支持這種方式。如果這行代碼拋出了錯(cuò)誤暑始,那么說(shuō)明是 IE搭独,于是就必須使用 text 屬性了。
??整個(gè)過(guò)程可以用以下函數(shù)來(lái)表示:

function loadScriptString(code){
    var script = document.createElement("script");
    script.type = "text/javascript";
    try {
        script.appendChild(document.createTextNode(code));
    } catch (ex){
        script.text = code;
    }
    document.body.appendChild(script);
}

??下面是調(diào)用這個(gè)函數(shù)的示例:

loadScriptString("function sayHi(){console.log('hi');}"); 

??以這種方式加載的代碼會(huì)在全局作用域中執(zhí)行廊镜,而且當(dāng)腳本執(zhí)行后將立即可用牙肝。實(shí)際上,這樣執(zhí)行代碼與在全局作用域中把相同的字符串傳遞給 eval() 是一樣的嗤朴。

2.2配椭、動(dòng)態(tài)樣式

??能夠把 CSS 樣式包含到 HTML 頁(yè)面中的元素有兩個(gè)。其中雹姊,<link> 元素用于包含來(lái)自外部的文件股缸,而 <style> 元素用于指定嵌入的樣式。
??用動(dòng)態(tài)腳本類似吱雏,所謂動(dòng)態(tài)樣式是指在頁(yè)面剛加載時(shí)不存在的樣式敦姻;動(dòng)態(tài)樣式是在頁(yè)面加載完成后動(dòng)態(tài)添加到頁(yè)面中的。
??以下面這個(gè)典型的 <link> 元素為例:

<link rel="stylesheet" type="text/css" href="styles.css"> 

??使用 DOM 代碼可以很容易地動(dòng)態(tài)創(chuàng)建出這個(gè)元素:

var link = document.createElement("link");
link.rel = "stylesheet";
link.type = "text/css";
link.href = "style.css";
var head = document.getElementsByTagName("head")[0];
head.appendChild(link);

??以上代碼在所有主流瀏覽器中都可以正常運(yùn)行歧杏。需要注意的是镰惦,必須將 <link> 元素添加到 <head> 而不是 <body> 元素,才能保證在所有瀏覽器中的行為一致得滤。整個(gè)過(guò)程可以用以下函數(shù)來(lái)表示:

function loadStyles(url){
    var link = document.createElement("link"); 
    link.rel = "stylesheet";
    link.type = "text/css";
    link.href = url;
    var head = document.getElementsByTagName("head")[0];
    head.appendChild(link);
} 

??調(diào)用 loadStyles() 函數(shù)的代碼如下所示:

loadStyles("styles.css");

??加載外部樣式文件的過(guò)程是異步的陨献,也就是加載樣式與執(zhí)行 JavaScript 代碼的過(guò)程沒(méi)有固定的次序。
??一般來(lái)說(shuō)懂更,知不知道樣式已經(jīng)加載完成并不重要眨业;不過(guò),也存在幾種利用事件來(lái)檢測(cè)這個(gè)過(guò)程是否完成的技術(shù)沮协。

??另一種定義樣式的方式是使用 <style> 元素來(lái)包含嵌入式 CSS龄捡,如下所示:

<style type="text/css">
body {
    background-color: red;
}
</style>

??按照相同的邏輯,下列 DOM 代碼應(yīng)該是有效的:

var style = document.createElement("style");
style.type = "text/css";
style.appendChild(document.createTextNode("body{background-color:red}"));
var head = document.getElementsByTagName("head")[0];
head.appendChild(style);

??以上代碼可以在 Firefox慷暂、Safari聘殖、Chrome 和 Opera 中運(yùn)行晨雳,在 IE 中則會(huì)報(bào)錯(cuò)。IE 將 <style> 視為一個(gè)特殊的奸腺、與 <script> 類似的節(jié)點(diǎn)餐禁,不允許訪問(wèn)其子節(jié)點(diǎn)。
??事實(shí)上突照,IE 此時(shí)拋出的錯(cuò)誤與向 <script> 元素添加子節(jié)點(diǎn)時(shí)拋出的錯(cuò)誤相同帮非。解決 IE 中這個(gè)問(wèn)題的辦法,就是訪問(wèn)元素的 styleSheet 屬性讹蘑,該屬性又有一個(gè) cssText 屬性末盔,可以接受 CSS 代碼,如下面的例子所示座慰。

var style = document.createElement("style");
style.type = "text/css";
try{
    style.appendChild(document.createTextNode("body{background-color:red}"));
} catch (ex){
    style.styleSheet.cssText = "body{background-color:red}";
}
var head = document.getElementsByTagName("head")[0];
head.appendChild(style);

??與動(dòng)態(tài)添加嵌入式腳本類似陨舱,重寫后的代碼使用了 try-catch 語(yǔ)句來(lái)捕獲 IE 拋出的錯(cuò)誤,然后再使用針對(duì) IE 的特殊方式來(lái)設(shè)置樣式版仔。因此游盲,通用的解決方案如下。

function loadStyleString(css){
    var style = document.createElement("style"); 
    style.type = "text/css";
    try{
        style.appendChild(document.createTextNode(css));
    } catch (ex){
        style.styleSheet.cssText = css;
    }
    var head = document.getElementsByTagName("head")[0];
    head.appendChild(style);
} 

??調(diào)用這個(gè)函數(shù)的示例如下:

loadStyleString("body{background-color:red}");

??這種方式會(huì)實(shí)時(shí)地向頁(yè)面中添加樣式邦尊,因此能夠馬上看到變化背桐。

??如果專門針對(duì) IE 編寫代碼,務(wù)必小心使用 styleSheet.cssText 屬性蝉揍。在重用同一個(gè) <style> 元素并再次設(shè)置這個(gè)屬性時(shí)链峭,有可能會(huì)導(dǎo)致瀏覽器崩潰。同樣又沾,將 cssText 屬性設(shè)置為空字符串也可能導(dǎo)致瀏覽器崩潰弊仪。我們希望 IE 中的這個(gè) bug 能夠在將來(lái)被修復(fù)。

2.3杖刷、操作表格

??<table> 元素是 HTML 中最復(fù)雜的結(jié)構(gòu)之一励饵。要想創(chuàng)建表格,一般都必須涉及表示表格行滑燃、單元格役听、表頭等方面的標(biāo)簽。
??由于涉及的標(biāo)簽多表窘,因而使用核心 DOM 方法創(chuàng)建和修改表格往往都免不了要編寫大量的代碼典予。假設(shè)我們要使用 DOM 來(lái)創(chuàng)建下面的 HTML 表格。

<table border="1" width="100%">
    <tbody>
        <tr>
            <td>Cell 1,1</td>
            <td>Cell 2,1</td>
        </tr>
        <tr>
            <td>Cell 1,2</td>
            <td>Cell 2,2</td>
        </tr>
    </tbody>
</table>

??要使用核心 DOM 方法創(chuàng)建這些元素乐严,得需要像下面這么多的代碼:

// 創(chuàng)建 table
var table = document.createElement("table");
table.border = 1;
table.width = "100%";

// 創(chuàng)建 tbody
var tbody = document.createElement("tbody"); 
table.appendChild(tbody);

// 創(chuàng)建第一行
var row1 = document.createElement("tr");
tbody.appendChild(row1);
var cell1_1 = document.createElement("td");
cell1_1.appendChild(document.createTextNode("Cell 1,1"));
row1.appendChild(cell1_1);
var cell2_1 = document.createElement("td");
cell2_1.appendChild(document.createTextNode("Cell 2,1"));
row1.appendChild(cell2_1);

// 創(chuàng)建第二行
var row2 = document.createElement("tr");
tbody.appendChild(row2);
var cell1_2 = document.createElement("td");
cell1_2.appendChild(document.createTextNode("Cell 1,2"));
row2.appendChild(cell1_2);
var cell2_2= document.createElement("td");
cell2_2.appendChild(document.createTextNode("Cell 2,2"));
row2.appendChild(cell2_2);

// 將表格添加到文檔主體中
document.body.appendChild(table);

??顯然瘤袖,DOM 代碼很長(zhǎng),還有點(diǎn)不太好懂昂验。為了方便構(gòu)建表格捂敌,HTML DOM 還為<table>艾扮、<tbody> 和 <tr> 元素添加了一些屬性和方法。

??為 <table> 元素添加的屬性和方法如下占婉。

  • caption:保存著對(duì) <caption> 元素(如果有)的指針泡嘴。
  • tHead:保存著對(duì) <thead> 元素(如果有)的指針。
  • tBodies:是一個(gè) <tbody> 元素的 HTMLCollection锐涯。
  • tFoot:保存著對(duì) <tfoot> 元素(如果有)的指針磕诊。
  • rows:是一個(gè)表格中所有行的 HTMLCollection。
  • createCaption():創(chuàng)建 <caption> 元素纹腌,將其放到表格中,返回引用滞磺。
  • createTHead():創(chuàng)建 <thead> 元素升薯,將其放到表格中,返回引用击困。
  • createTFoot():創(chuàng)建 <tfoot> 元素涎劈,將其放到表格中,返回引用阅茶。
  • deleteCaption():刪除 <caption> 元素蛛枚。
  • deleteTHead():刪除 <thead> 元素。
  • deleteTFoot():刪除 <tfoot> 元素脸哀。
  • deleteRow(pos):刪除指定位置的行蹦浦。
  • insertRow(pos):向 rows 集合中的指定位置插入一行。

??為 <tbody> 元素添加的屬性和方法如下撞蜂。

  • rows:保存著 <tbody> 元素中行的 HTMLCollection盲镶。
  • deleteRow(pos):刪除指定位置的行。
  • insertRow(pos):向 rows 集合中的指定位置插入一行蝌诡,返回對(duì)新插入行的引用溉贿。

??為 <tr> 元素添加的屬性和方法如下。

  • cells:保存著 <tr> 元素中單元格的 HTMLCollection浦旱。
  • deleteCell(pos):刪除指定位置的單元格宇色。
  • insertCell(pos):向 cells 集合中的指定位置插入一個(gè)單元格,返回對(duì)新插入單元格的引用颁湖。

??使用這些屬性和方法宣蠕,可以極大地減少創(chuàng)建表格所需的代碼數(shù)量。例如爷狈,使用這些屬性和方法可以將前面的代碼重寫如下植影。

// 創(chuàng)建 table
var table = document.createElement("table");
table.border = 1;
table.width = "100%";

// 創(chuàng)建 tbody
var tbody = document.createElement("tbody");
table.appendChild(tbody);

// 創(chuàng)建第一行
tbody.insertRow(0);
tbody.rows[0].insertCell(0);
tbody.rows[0].cells[0].appendChild(document.createTextNode("Cell 1,1"));
tbody.rows[0].insertCell(1);
tbody.rows[0].cells[1].appendChild(document.createTextNode("Cell 2,1"));

// 創(chuàng)建第二行
tbody.insertRow(1);
tbody.rows[1].insertCell(0);
tbody.rows[1].cells[0].appendChild(document.createTextNode("Cell 1,2"));
tbody.rows[1].insertCell(1);
tbody.rows[1].cells[1].appendChild(document.createTextNode("Cell 2,2"));

// 將表格添加到文檔主體中
document.body.appendChild(table);

??在這次的代碼中,創(chuàng)建 <table> 和 <tbody> 的代碼沒(méi)有變化涎永。不同的是創(chuàng)建兩行的部分思币,其中使用了 HTML DOM 定義的表格屬性和方法鹿响。
??在創(chuàng)建第一行時(shí),通過(guò) <tbody> 元素調(diào)用了 insertRow() 方法谷饿,傳入了參數(shù) 0 —— 表示應(yīng)該將插入的行放在什么位置上惶我。執(zhí)行這一行代碼后,就會(huì)自動(dòng)創(chuàng)建一行并將其插入到 <tbody> 元素的位置 0 上博投,因此就可以馬上通過(guò) tbody.rows[0] 來(lái)引用新插入的行绸贡。
??創(chuàng)建單元格的方式也十分相似,即通過(guò) <tr> 元素調(diào)用 insertCell() 方法并傳入放置單元格的位置毅哗。然后听怕,就可以通過(guò) tbody.rows[0].cells[0] 來(lái)引用新插入的單元格,因?yàn)樾聞?chuàng)建的單元格被插
入到了這一行的位置 0 上虑绵。
??總之尿瞭,使用這些屬性和方法創(chuàng)建表格的邏輯性更強(qiáng),也更容易看懂翅睛,盡管技術(shù)上這兩套代碼都是正確的声搁。

2.4、使用 NodeList

??理解 NodeList 及其 “近親” NameNodeMap 和 HTMLCollection捕发,是從整體上透徹理解 DOM 的關(guān)鍵所在疏旨。這三個(gè)集合都是“動(dòng)態(tài)的”;換句話說(shuō)扎酷,每當(dāng)文檔結(jié)構(gòu)發(fā)生變化時(shí)檐涝,它們都會(huì)得到更新。因此霞玄,它們始終都會(huì)保持著最新骤铃、最準(zhǔn)確的信息。
??從本質(zhì)上說(shuō)坷剧,所有 NodeList 對(duì)象都是在訪問(wèn) DOM 文檔時(shí)實(shí)時(shí)運(yùn)行的查詢惰爬。例如,下列代碼會(huì)導(dǎo)致無(wú)限循環(huán):

var divs = document.getElementsByTagName("div"),
    i,
    div;
for (i=0; i < divs.length; i++){
    div = document.createElement("div");
    document.body.appendChild(div);
} 

??第一行代碼會(huì)取得文檔中所有<div>元素的 HTMLCollection惫企。由于這個(gè)集合是“動(dòng)態(tài)的”撕瞧,因此只要有新<div>元素被添加到頁(yè)面中,這個(gè)元素也會(huì)被添加到該集合中狞尔。瀏覽器不會(huì)將創(chuàng)建的所有集合都保存在一個(gè)列表中丛版,而是在下一次訪問(wèn)集合時(shí)再更新集合。結(jié)果偏序,在遇到上例中所示的循環(huán)代碼時(shí)页畦,就會(huì)導(dǎo)致一個(gè)有趣的問(wèn)題。每次循環(huán)都要對(duì)條件 i < divs.length 求值研儒,意味著會(huì)運(yùn)行取得所有<div>元素的查詢豫缨。
??考慮到循環(huán)體每次都會(huì)創(chuàng)建一個(gè)新<div>元素并將其添加到文檔中独令,因此divs.length 的值在每次循環(huán)后都會(huì)遞增。既然 i 和 divs.length 每次都會(huì)同時(shí)遞增好芭,結(jié)果它們的值永遠(yuǎn)也不會(huì)相等燃箭。

??如果想要迭代一個(gè) NodeList,最好是使用 length 屬性初始化第二個(gè)變量舍败,然后將迭代器與該變量進(jìn)行比較招狸,如下面的例子所示:

var divs = document.getElementsByTagName("div"),
    i,
    len,
    div;
for (i=0, len=divs.length; i < len; i++){
    div = document.createElement("div");
    document.body.appendChild(div);
} 

??上述例子中初始化了第二個(gè)變量 len。由于 len 中保存著對(duì) divs.length 在循環(huán)開(kāi)始時(shí)的一個(gè)快照邻薯,因此就會(huì)避免上一個(gè)例子中出現(xiàn)的無(wú)限循環(huán)問(wèn)題裙戏。
??在本章演示迭代 NodeList 對(duì)象的例子中,使用的都是這種更為保險(xiǎn)的方式弛说。
??一般來(lái)說(shuō)挽懦,應(yīng)該盡量減少訪問(wèn) NodeList 的次數(shù)。因?yàn)槊看卧L問(wèn) NodeList木人,都會(huì)運(yùn)行一次基于文檔的查詢。所以冀偶,可以考慮將從 NodeList 中取得的值緩存起來(lái)醒第。

小結(jié)

??DOM 是語(yǔ)言中立的 API,用于訪問(wèn)和操作 HTML 和 XML 文檔进鸠。DOM1 級(jí)將 HTML 和 XML 文檔形象地看作一個(gè)層次化的節(jié)點(diǎn)樹(shù)稠曼,可以使用 JavaScript 來(lái)操作這個(gè)節(jié)點(diǎn)樹(shù),進(jìn)而改變底層文檔的外觀和結(jié)構(gòu)客年。

??DOM 由各種節(jié)點(diǎn)構(gòu)成霞幅,簡(jiǎn)要總結(jié)如下。

  • 最基本的節(jié)點(diǎn)類型是 Node量瓜,用于抽象地表示文檔中一個(gè)獨(dú)立的部分司恳;所有其他類型都繼承自 Node。
  • Document 類型表示整個(gè)文檔绍傲,是一組分層節(jié)點(diǎn)的根節(jié)點(diǎn)扔傅。在 JavaScript 中,document 對(duì)象是 Document 的一個(gè)實(shí)例烫饼。使用 document 對(duì)象猎塞,有很多種方式可以查詢和取得節(jié)點(diǎn)。
  • Element 節(jié)點(diǎn)表示文檔中的所有 HTML 或 XML 元素杠纵,可以用來(lái)操作這些元素的內(nèi)容和特性荠耽。
  • 另外還有一些節(jié)點(diǎn)類型,分別表示文本內(nèi)容比藻、注釋铝量、文檔類型倘屹、CDATA 區(qū)域和文檔片段。

??訪問(wèn) DOM 的操作在多數(shù)情況下都很直觀款违,不過(guò)在處理 <script> 和 <style> 元素時(shí)還是存在一些復(fù)雜性唐瀑。
??由于這兩個(gè)元素分別包含腳本和樣式信息,因此瀏覽器通常會(huì)將它們與其他元素區(qū)別對(duì)待插爹。這些區(qū)別導(dǎo)致了在針對(duì)這些元素使用 innerHTML 時(shí)哄辣,以及在創(chuàng)建新元素時(shí)的一些問(wèn)題。
??理解 DOM 的關(guān)鍵赠尾,就是理解 DOM 對(duì)性能的影響力穗。DOM 操作往往是 JavaScript 程序中開(kāi)銷最大的部分,而因訪問(wèn) NodeList 導(dǎo)致的問(wèn)題為最多气嫁。
??NodeList 對(duì)象都是“動(dòng)態(tài)的”当窗,這就意味著每次訪問(wèn) NodeList 對(duì)象,都會(huì)運(yùn)行一次查詢寸宵。有鑒于此崖面,最好的辦法就是盡量減少 DOM 操作。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末梯影,一起剝皮案震驚了整個(gè)濱河市巫员,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌甲棍,老刑警劉巖简识,帶你破解...
    沈念sama閱讀 206,013評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異感猛,居然都是意外死亡称诗,警方通過(guò)查閱死者的電腦和手機(jī)主经,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門偏竟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)硬纤,“玉大人,你說(shuō)我怎么就攤上這事拷泽∫呷担” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 152,370評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵司致,是天一觀的道長(zhǎng)拆吆。 經(jīng)常有香客問(wèn)我,道長(zhǎng)脂矫,這世上最難降的妖魔是什么枣耀? 我笑而不...
    開(kāi)封第一講書人閱讀 55,168評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上捞奕,老公的妹妹穿的比我還像新娘牺堰。我一直安慰自己,他們只是感情好颅围,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布伟葫。 她就那樣靜靜地躺著,像睡著了一般院促。 火紅的嫁衣襯著肌膚如雪筏养。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 48,954評(píng)論 1 283
  • 那天常拓,我揣著相機(jī)與錄音渐溶,去河邊找鬼。 笑死弄抬,一個(gè)胖子當(dāng)著我的面吹牛茎辐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播掂恕,決...
    沈念sama閱讀 38,271評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼拖陆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了懊亡?” 一聲冷哼從身側(cè)響起慕蔚,我...
    開(kāi)封第一講書人閱讀 36,916評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎斋配,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體灌闺,經(jīng)...
    沈念sama閱讀 43,382評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡艰争,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了桂对。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片甩卓。...
    茶點(diǎn)故事閱讀 37,989評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蕉斜,靈堂內(nèi)的尸體忽然破棺而出逾柿,到底是詐尸還是另有隱情,我是刑警寧澤宅此,帶...
    沈念sama閱讀 33,624評(píng)論 4 322
  • 正文 年R本政府宣布机错,位于F島的核電站,受9級(jí)特大地震影響父腕,放射性物質(zhì)發(fā)生泄漏弱匪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評(píng)論 3 307
  • 文/蒙蒙 一璧亮、第九天 我趴在偏房一處隱蔽的房頂上張望萧诫。 院中可真熱鬧斥难,春花似錦、人聲如沸帘饶。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,199評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)及刻。三九已至镀裤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間提茁,已是汗流浹背淹禾。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,418評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留茴扁,地道東北人铃岔。 一個(gè)月前我還...
    沈念sama閱讀 45,401評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像峭火,于是被迫代替她去往敵國(guó)和親毁习。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容