十一揪利、DOM2 和DOM 3

??DOM 1 級主要定義的是 HTML 和 XML 文檔的底層結(jié)構(gòu)。

??DOM2 和 DOM3 級則在這個結(jié)構(gòu)的基礎(chǔ)上引入了更多的的交互能力,也支持了更高級的 XML 特性簇宽。

??為此勋篓,DOM2 和 DOM3 級分為許多模塊(模塊之間具有某種關(guān)聯(lián)),分別描述了 DOM 的某個非常具體的子集魏割。這些模塊如下譬嚣。

  • DOM2 級核心(DOM Level 2 Core):在 1 級核心基礎(chǔ)上構(gòu)建,為節(jié)點添加了更多方法和屬性钞它。
  • DOM2 級視圖(DOM Level 2 Views):為文檔定義了基于樣式信息的不同視圖拜银。
  • DOM2 級 HTML(DOM Level 2 HTML):在 1 級 HTML 基礎(chǔ)上構(gòu)建,添加了更多屬性须揣、方法和新接口盐股。
  • DOM2 級事件(DOM Level 2 Events):說明了如何使用事件與 DOM 文檔交互。
  • DOM2 級樣式(DOM Level 2 Style):定義了如何以編程方式來訪問和改變 CSS 樣式信息耻卡。
  • DOM2 級遍歷和范圍(DOM Level 2 Traversal and Range):引入了遍歷 DOM 文檔和選擇其特定部分的新接口疯汁。

??這里探討除 “DOM2 級事件” 之外的所有模塊。

1卵酪、DOM 變化

??DOM2 級和 3 級的目的在于擴(kuò)展 DOM API幌蚊,以滿足操作 XML 的所有需求,同時提供更好的錯誤處理及特性檢測能力溃卡。從某種意義上講溢豆,實現(xiàn)這一目的很大程度意味著對命名空間的支持。
??“DOM2 級核心” 沒有引入新類型瘸羡,它只是在 DOM1 級的基礎(chǔ)上通過增加新方法和新屬性來增強(qiáng)了既有類型漩仙。
??“DOM3 級核心” 同樣增強(qiáng)了既有類型,但也引入了一些新類型犹赖。

??類似的队他,“DOM2 級視圖” 和 “DOM2 級 HTML” 模塊也增強(qiáng)了 DOM 接口,提供了新的屬性和方法峻村。由于這兩個模塊很小麸折,因此我們將把它們與 “DOM2 級核心” 放在一起,討論基本 JavaScript 對象的變化粘昨。
??可以通過下列代碼來確定瀏覽器是否支持這些 DOM 模塊垢啼。

var supportsDOM2Core = document.implementation.hasFeature("Core", "2.0");
var supportsDOM3Core = document.implementation.hasFeature("Core", "3.0");
var supportsDOM2HTML = document.implementation.hasFeature("HTML", "2.0");
var supportsDOM2Views = document.implementation.hasFeature("Views", "2.0");
var supportsDOM2XML = document.implementation.hasFeature("XML", "2.0"); 

1.1、針對 XML 命名空間的變化

??有了 XML 命名空間张肾,不同 XML 文檔的元素就可以混合在一起芭析,共同構(gòu)成格式良好的文檔,而不必?fù)?dān)心發(fā)生命名沖突吞瞪。
??從技術(shù)上說放刨,HTML 不支持 XML 命名空間,但 XHTML 支持 XML 命名空間尸饺。因此进统,本節(jié)給出的都是 XHTML 的示例。
??命名空間要使用 xmlns 特性來指定浪听。XHTML 的命名空間是 http://www.w3.org/1999/xhtml螟碎,在任何格式良好 XHTML 頁面中,都應(yīng)該將其包含在<html>元素中迹栓,如下面的例子所示掉分。

<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>Example XHTML page</title>
    </head>
    <body>
        Hello world!
    </body>
</html>

??對上述例子而言,其中的所有元素默認(rèn)都被視為 XHTML 命名空間中的元素克伊。要想明確地為 XML 命名空間創(chuàng)建前綴酥郭,可以使用 xmlns 后跟冒號,再后跟前綴愿吹,如下所示不从。

<xhtml:html xmlns:xhtml="http://www.w3.org/1999/xhtml">
    <xhtml:head>
        <xhtml:title>Example XHTML page</xhtml:title>
    </xhtml:head>
    <xhtml:body>
        Hello world!
    </xhtml:body>
</xhtml:html>

??這里為 XHTML 的命名空間定義了一個名為 xhtml 的前綴,并要求所有 XHTML 元素都以該前綴開頭犁跪。有時候為了避免不同語言間的沖突椿息,也需要使用命名空間來限定特性,示例:

<xhtml:html xmlns:xhtml="http://www.w3.org/1999/xhtml">
    <xhtml:head>
        <xhtml:title>Example XHTML page</xhtml:title>
    </xhtml:head>
    <xhtml:body xhtml:class="home">
        Hello world!
    </xhtml:body>
</xhtml:html>

??這個例子中的特性 class 帶有一個 xhtml 前綴坷衍。在只基于一種語言編寫 XML 文檔的情況下寝优,命名空間實際上也沒有什么用。不過枫耳,在混合使用兩種語言的情況下乏矾,命名空間的用處就非常大了。
??來看一看下面這個混合了 XHTML 和 SVG 語言的文檔:

<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>Example XHTML page</title>
    </head>
    <body>
        <svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 100 100" style="width:100%; height:100%">
            <rect x="0" y="0" width="100" height="100" style="fill:red"/>
        </svg>
    </body>
</html>

??在上述例子中迁杨,通過設(shè)置命名空間钻心,將<svg>標(biāo)識為了與包含文檔無關(guān)的元素。此時仑最,<svg>元素的所有子元素扔役,以及這些元素的所有特性,都被認(rèn)為屬于 http://www.w3.org/2000/svg 命名空間警医。
??即使這個文檔從技術(shù)上說是一個 XHTML 文檔亿胸,但因為有了命名空間,其中的 SVG代碼也仍然是有效的预皇。
??對于類似這樣的文檔來說侈玄,最有意思的事發(fā)生在調(diào)用方法操作文檔節(jié)點的情況下。例如吟温,在創(chuàng)建一個元素時序仙,這個元素屬于哪個命名空間呢?在查詢一個特殊標(biāo)簽名時鲁豪,應(yīng)該將結(jié)果包含在哪個命名空間中呢潘悼?
??“DOM2 級核心” 通過為大多數(shù) DOM1 級方法提供特定于命名空間的版本解決了這個問題律秃。

1. Node 類型的變化

??在 DOM2 級中,Node 類型包含下列特定于命名空間的屬性治唤。

  • localName:不帶命名空間前綴的節(jié)點名稱棒动。
  • namespaceURI:命名空間 URI 或者(在未指定的情況下是)null。
  • prefix:命名空間前綴或者(在未指定的情況下是)null宾添。

??當(dāng)節(jié)點使用了命名空間前綴時船惨,其 nodeName 等于 prefix+":"+ localName。以下面的文檔為例:

<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>Example XHTML page</title>
    </head>
    <body>
        <s:svg xmlns:s="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 100 100" style="width:100%; height:100%">
            <s:rect x="0" y="0" width="100" height="100" style="fill:red"/>
        </s:svg>
    </body>
</html>

??對于<html>元素來說缕陕,它的 localName 和 tagName 是"html"粱锐,namespaceURI 是 "http://www.w3.org/1999/xhtml",而 prefix 是 null扛邑。

??對于<s:svg>元素而言怜浅,它的 localName 是"svg",
tagName 是"s:svg"鹿榜,namespaceURI 是"http://www.w3.org/2000/svg"海雪,而 prefix 是"s"。

??DOM3 級在此基礎(chǔ)上更進(jìn)一步舱殿,又引入了下列與命名空間有關(guān)的方法奥裸。

  • isDefaultNamespace(namespaceURI):在指定的 namespaceURI 是當(dāng)前節(jié)點的默認(rèn)命名空間的情況下返回 true。
  • lookupNamespaceURI(prefix):返回給定 prefix 的命名空間沪袭。
  • lookupPrefix(namespaceURI):返回給定 namespaceURI 的前綴湾宙。

??針對前面的例子,可以執(zhí)行下列代碼:

console.log(document.body.isDefaultNamespace("http://www.w3.org/1999/xhtml"); // true

// 假設(shè) svg 中包含著對<s:svg>的引用
console.log(svg.lookupPrefix("http://www.w3.org/2000/svg")); //"s"
console.log(svg.lookupNamespaceURI("s")); //"http://www.w3.org/2000/svg"

??在取得了一個節(jié)點冈绊,但不知道該節(jié)點與文檔其他元素之間關(guān)系的情況下侠鳄,這些方法是很有用的。

2. Document 類型的變化

??DOM2 級中的 Document 類型也發(fā)生了變化死宣,包含了下列與命名空間有關(guān)的方法伟恶。

  • createElementNS(namespaceURI, tagName):使用給定的 tagName 創(chuàng)建一個屬于命名空間 namespaceURI 的新元素。
  • createAttributeNS(namespaceURI, attributeName):使用給定的 attributeName 創(chuàng)建一個屬于命名空間 namespaceURI 的新特性毅该。
  • getElementsByTagNameNS(namespaceURI, tagName):返回屬于命名空間 namespaceURI 的 tagName 元素的 NodeList博秫。

??使用這些方法時需要傳入表示命名空間的 URI(而不是命名空間前綴),如下面的例子所示眶掌。

// 創(chuàng)建一個新的 SVG 元素
var svg = document.createElementNS("http://www.w3.org/2000/svg","svg");

// 創(chuàng)建一個屬于某個命名空間的新特性
var att = document.createAttributeNS("http://www.somewhere.com", "random");

// 取得所有 XHTML 元素
var elems = document.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "*");

??只有在文檔中存在兩個或多個命名空間時挡育,這些與命名空間有關(guān)的方法才是必需的。

3. Element 類型的變化

??“DOM2 級核心”中有關(guān) Element 的變化朴爬,主要涉及操作特性即寒。新增的方法如下。

  • getAttributeNS(namespaceURI, localName):取得屬于命名空間 namespaceURI 且名為 localName 的特性。
  • getAttributeNodeNS(namespaceURI, localName):取得屬于命名空間 namespaceURI 且名為 localName 的特性節(jié)點母赵。
  • getElementsByTagNameNS(namespaceURI, tagName):返回屬于命名空間 namespaceURI 的 tagName 元素的 NodeList逸爵。
  • hasAttributeNS(namespaceURI,localName):確定當(dāng)前元素是否有一個名為 localName 的特性,而且該特性的命名空間是 namespaceURI凹嘲。注意痊银,“DOM2 級核心”也增加了一個 hasAttribute() 方法,用于不考慮命名空間的情況施绎。
  • removeAttriubteNS(namespaceURI,localName):刪除屬于命名空間 namespaceURI 且名為 localName 的特性。
  • setAttributeNS(namespaceURI,qualifiedName,value):設(shè)置屬于命名空間 namespaceURI 且名為 qualifiedName 的特性的值為 value贞绳。
  • setAttributeNodeNS(attNode):設(shè)置屬于命名空間 namespaceURI 的特性節(jié)點谷醉。

??除了第一個參數(shù)之外,這些方法與 DOM1 級中相關(guān)方法的作用相同冈闭;第一個參數(shù)始終都是一個命名空間 URI俱尼。

4. NamedNodeMap 類型的變化

??NamedNodeMap 類型也新增了下列與命名空間有關(guān)的方法。由于特性是通過 NamedNodeMap 表示的萎攒,因此這些方法多數(shù)情況下只針對特性使用遇八。

  • getNamedItemNS(namespaceURI, localName):取得屬于命名空間 namespaceURI 且名為 localName 的項。
  • removeNamedItemNS(namespaceURI, localName):移除屬于命名空間 namespaceURI 且名為 localName 的項耍休。
  • setNamedItemNS(node):添加 node刃永,這個節(jié)點已經(jīng)事先指定了命名空間信息。

??由于一般都是通過元素訪問特性羊精,所以這些方法很少使用斯够。

1.2、其他方面的變化

??DOM 的其他部分在“DOM2 級核心”中也發(fā)生了一些變化喧锦。這些變化與 XML 命名空間無關(guān)读规,而是更傾向于確保 API 的可靠性及完整性。

1. DocumentType 類型的變化

??DocumentType 類型新增了 3 個屬性:publicId燃少、systemId 和 internalSubset.

??其中束亏,前兩個屬性表示的是文檔類型聲明中的兩個信息段瘸恼,這兩個信息段在 DOM1 級中是沒有辦法訪問到的肯污。以下面的 HTML 文檔類型聲明為例。

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

??對這個文檔類型聲明而言世澜,publicId 是"-//W3C//DTD HTML 4.01//EN"怔昨,而 systemId 是 "http://www.w3.org/TR/html4/strict.dtd"雀久。

??在支持 DOM2 級的瀏覽器中,應(yīng)該可以運行下列代碼趁舀。

console.log(document.doctype.publicId);
console.log(document.doctype.systemId);

??實際上赖捌,很少需要在網(wǎng)頁中訪問此類信息。

??最后一個屬性 internalSubset,用于訪問包含在文檔類型聲明中的額外定義越庇,以下面的代碼為例罩锐。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
[<!ELEMENT name (#PCDATA)>] >

??訪問 document.doctype.internalSubset 將得到 "<!ELEMENT name (#PCDATA)>"

??這種內(nèi)部子集(internal subset)在 HTML 中極少用到卤唉,在 XML 中可能會更常見一些涩惑。

2. Document 類型的變化

??Document 類型的變化中唯一與命名空間無關(guān)的方法是 importNode()。
??這個方法的用途是從一個文檔中取得一個節(jié)點桑驱,然后將其導(dǎo)入到另一個文檔竭恬,使其成為這個文檔結(jié)構(gòu)的一部分。
??需要注意的是熬的,每個節(jié)點都有一個 ownerDocument 屬性痊硕,表示所屬的文檔。如果調(diào)用 appendChild() 時傳入的節(jié)點屬于不同的文檔(ownerDocument 屬性的值不一樣)押框,則會導(dǎo)致錯誤岔绸。
??但在調(diào)用 importNode() 時傳入不同文檔的節(jié)點則會返回一個新節(jié)點,這個新節(jié)點的所有權(quán)歸當(dāng)前文檔所有橡伞。
??說起來盒揉,importNode() 方法與 Element 的 cloneNode() 方法非常相似,它接受兩個參數(shù):要復(fù)制的節(jié)點和一個表示是否復(fù)制子節(jié)點的布爾值兑徘。返回的結(jié)果是原來節(jié)點的副本刚盈,但能夠在當(dāng)前文檔中使用。示例:

var newNode = document.importNode(oldNode, true); // 導(dǎo)入節(jié)點及其所有子節(jié)點
document.body.appendChild(newNode);

??這個方法在 HTML 文檔中并不常用道媚,在 XML 文檔中用得比較多扁掸。

??“DOM2 級視圖”模塊添加了一個名為 defaultView 的屬性,其中保存著一個指針最域,指向擁有給定文檔的窗口(或框架)谴分。
??除此之外,“視圖”規(guī)范沒有提供什么時候其他視圖可用的信息镀脂,因而這是唯一一個新增的屬性牺蹄。

??除 IE 之外的所有瀏覽器都支持 defaultView 屬性。

??在 IE 中有一個等價的屬性名叫 parentWindow(Opera 也支持這個屬性)薄翅。

??因此沙兰,要確定文檔的歸屬窗口,可以使用以下代碼翘魄。

var parentWindow = document.defaultView || document.parentWindow;

??除了上述一個方法和一個屬性之外鼎天,“DOM2級核心”還為 document.implementation 對象規(guī)定了兩個新方法:createDocumentType() 和 createDocument()。
??前者用于創(chuàng)建一個新的 DocumentType 節(jié)點暑竟,接受 3 個參數(shù):文檔類型名稱斋射、publicId、systemId。例如罗岖,下列代碼會創(chuàng)建一個新的 HTML4.01 Strict 文檔類型涧至。

var doctype = document.implementation.createDocumentType("html",
                  "-//W3C//DTD HTML 4.01//EN",
                  "http://www.w3.org/TR/html4/strict.dtd");

??由于既有文檔的文檔類型不能改變,因此 createDocumentType() 只在創(chuàng)建新文檔時有用桑包;

??創(chuàng)建新文檔時需要用到 createDocument() 方法南蓬。這個方法接受 3 個參數(shù):針對文檔中元素的 namespaceURI、文檔元素的標(biāo)簽名哑了、新文檔的文檔類型赘方。下面這行代碼將會創(chuàng)建一個空的新XML 文檔。

var doc = document.implementation.createDocument("", "root", null);

??這行代碼會創(chuàng)建一個沒有命名空間的新文檔弱左,文檔元素為<root>蒜焊,而且沒有指定文檔類型。

??要想創(chuàng)建一個 XHTML 文檔科贬,可以使用以下代碼。

var doctype = document.implementation.createDocumentType("html",
                  " -//W3C//DTD XHTML 1.0 Strict//EN",
                  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd");

var doc = document.implementation.createDocument("http://www.w3.org/1999/xhtml",
             "html", doctype);

??這樣鳖悠,就創(chuàng)建了一個帶有適當(dāng)命名空間和文檔類型的新 XHTML 文檔榜掌。不過,新文檔當(dāng)前只有文檔元素<html>乘综,剩下的所有元素都需要繼續(xù)添加憎账。

??“DOM2 級 HTML”模塊也為 document.implementation 新增了一個方法,名叫 createHTMLDocument()卡辰。
??這個方法的用途是創(chuàng)建一個完整的 HTML 文檔胞皱,包括<html>、<head>九妈、<title> 和 <body>元素反砌。
??這個方法只接受一個參數(shù),即新創(chuàng)建文檔的標(biāo)題(放在<title>元素中的字符串)萌朱,返回新的 HTML 文檔宴树,如下所示:

var htmldoc = document.implementation.createHTMLDocument("New Doc");
console.log(htmldoc.title); // "New Doc"
console.log(typeof htmldoc.body); // "object" 

??通過調(diào)用 createHTMLDocument() 創(chuàng)建的這個文檔,是 HTMLDocument 類型的實例晶疼,因而具有該類型的所有屬性和方法酒贬,包括 title 和 body 屬性。只有 Opera 和 Safari 支持這個方法翠霍。

3. Node 類型的變化

??Node 類型中唯一與命名空間無關(guān)的變化锭吨,就是添加了 isSupported() 方法。
??與 DOM1 級為 document.implementation 引入的 hasFeature() 方法類似寒匙,isSupported() 方法用于確定當(dāng)前節(jié)點具有什么能力零如。
??這個方法也接受相同的兩個參數(shù):特性名和特性版本號。如果瀏覽器實現(xiàn)了相應(yīng)特性,而且能夠基于給定節(jié)點執(zhí)行該特性埠况,isSupported() 就返回 true耸携。示例:

if (document.body.isSupported("HTML", "2.0")){
    // 執(zhí)行只有"DOM2 級 HTML"才支持的操作
}

??由于不同實現(xiàn)在決定對什么特性返回 true 或 false 時并不一致,這個方法同樣也存在與 hasFeature() 方法相同的問題辕翰。
??為此夺衍,我們建議在確定某個特性是否可用時,最好還是使用能力檢測喜命。

??DOM3 級引入了兩個輔助比較節(jié)點的方法:isSameNode() 和 isEqualNode()沟沙。

??這兩個方法都接受一個節(jié)點參數(shù),并在傳入節(jié)點與引用的節(jié)點相同或相等時返回 true壁榕。
??所謂相同矛紫,指的是兩個節(jié)點引用的是同一個對象。所謂相等牌里,指的是兩個節(jié)點是相同的類型颊咬,具有相等的屬性(nodeName、nodeValue牡辽,等等)喳篇,而且它們的 attributes 和 childNodes 屬性也相等(相同位置包含相同的值)。示例:

var div1 = document.createElement("div");
div1.setAttribute("class", "box");

var div2 = document.createElement("div");
div2.setAttribute("class", "box");

console.log(div1.isSameNode(div1)); // true
console.log(div1.isEqualNode(div2)); // true
console.log(div1.isSameNode(div2)); // false

??這里創(chuàng)建了兩個具有相同特性的<div>元素态辛。這兩個元素相等麸澜,但不相同。

??DOM3 級還針對為 DOM 節(jié)點添加額外數(shù)據(jù)引入了新方法奏黑。

??setUserData() 方法會將數(shù)據(jù)指定給節(jié)點炊邦,它接受 3 個參數(shù):要設(shè)置的鍵、實際的數(shù)據(jù)(可以是任何數(shù)據(jù)類型)和處理函數(shù)熟史。

??以下代碼可以將數(shù)據(jù)指定給一個節(jié)點馁害。

document.body.setUserData("name", "Nicholas", function(){});

??然后,使用 getUserData() 并傳入相同的鍵蹂匹,就可以取得該數(shù)據(jù)蜗细,如下所示:

var value = document.body.getUserData("name");

??傳入 setUserData() 中的處理函數(shù)會在帶有數(shù)據(jù)的節(jié)點被復(fù)制、刪除怒详、重命名或引入一個文檔時調(diào)用炉媒,因而你可以事先決定在上述操作發(fā)生時如何處理用戶數(shù)據(jù)。
??處理函數(shù)接受 5 個參數(shù):表示操作類型的數(shù)值(1 表示復(fù)制昆烁,2 表示導(dǎo)入吊骤,3 表示刪除,4 表示重命名)静尼、數(shù)據(jù)鍵白粉、數(shù)據(jù)值传泊、源節(jié)點和目標(biāo)節(jié)點。
??在刪除節(jié)點時鸭巴,源節(jié)點是 null眷细;除在復(fù)制節(jié)點時,目標(biāo)節(jié)點均為 null鹃祖。
??在函數(shù)內(nèi)部溪椎,你可以決定如何存儲數(shù)據(jù)。示例:

var div = document.createElement("div");
div.setUserData("name", "Nicholas", function(operation, key, value, src, dest){
    if (operation == 1){
        dest.setUserData(key, value, function(){}); 
    } 
});

var newDiv = div.cloneNode(true);
console.log(newDiv.getUserData("name")); // "Nicholas"

??這里恬口,先創(chuàng)建了一個<div>元素校读,然后又為它添加了一些數(shù)據(jù)(用戶數(shù)據(jù))。在使用 cloneNode() 復(fù)制這個元素時祖能,就會調(diào)用處理函數(shù)歉秫,從而將數(shù)據(jù)自動復(fù)制到了副本節(jié)點。結(jié)果在通過副本節(jié)點調(diào)用 getUserData() 時养铸,就會返回與原始節(jié)點中包含的相同的值雁芙。

4. 框架的變化

??框架和內(nèi)嵌框架分別用 HTMLFrameElement 和 HTMLIFrameElement 表示,它們在 DOM2級中都有了一個新屬性钞螟,名叫 contentDocument却特。
??這個屬性包含一個指針,指向表示框架內(nèi)容的文檔對象筛圆。在此之前,無法直接通過元素取得這個文檔對象(只能使用 frames 集合)椿浓√可以像下面這樣使用這個屬性。

var iframe = document.getElementById("myIframe");
var iframeDoc = iframe.contentDocument; // 在 IE8 以前的版本中無效

??由于 contentDocument 屬性是 Document 類型的實例扳碍,因此可以像使用其他 HTML 文檔一樣使用它提岔,包括所有屬性和方法。Opera笋敞、Firefox碱蒙、Safari 和 Chrome 支持這個屬性。
??IE8 之前不支持框中的 contentDocument 屬性夯巷,但支持一個名叫 contentWindow 的屬性赛惩,該屬性返回框架的 window 對象,而這個 window 對象又有一個 document 屬性趁餐。
??因此喷兼,要想在上述所有瀏覽器中訪問內(nèi)嵌框架的文檔對象,可以使用下列代碼后雷。

var iframe = document.getElementById("myIframe");
var iframeDoc = iframe.contentDocument || iframe.contentWindow.document;

??所有瀏覽器都支持 contentWindow 屬性季惯。

??訪問框架或內(nèi)嵌框架的文檔對象要受到跨域安全策略的限制吠各。如果某個框架中的頁面來自其他域或不同子域,或者使用了不同的協(xié)議勉抓,那么要訪問這個框架的文檔對象就會導(dǎo)致錯誤贾漏。

2、樣式

?? HTML 中定義樣式的方式有 3 種:通過<link/>元素包含外部樣式表文件藕筋、使用<style/>元素定義嵌入式樣式纵散,以及使用 style 特性定義針對特定元素的樣式。
??“DOM2 級樣式”模塊圍繞這 3 種應(yīng)用樣式的機(jī)制提供了一套 API念逞。要確定瀏覽器是否支持 DOM2 級定義的 CSS 能力困食,可以使用下列代碼。

var supportsDOM2CSS = document.implementation.hasFeature("CSS", "2.0");
var supportsDOM2CSS2 = document.implementation.hasFeature("CSS2", "2.0"); 

2.1翎承、訪問元素的樣式

??任何支持 style 特性的 HTML 元素在 JavaScript 中都有一個對應(yīng)的 style 屬性硕盹。這個 style 對象是 CSSStyleDeclaration 的實例,包含著通過 HTML 的 style 特性指定的所有樣式信息叨咖,但不包含與外部樣式表或嵌入樣式表經(jīng)層疊而來的樣式瘩例。
??在 style 特性中指定的任何 CSS 屬性都將表現(xiàn)為這個 style 對象的相應(yīng)屬性。對于使用短劃線(分隔不同的詞匯甸各,例如 background-image)的 CSS 屬性名垛贤,必須將其轉(zhuǎn)換成駝峰大小寫形式,才能通過 JavaScript 來訪問趣倾。下表列出了幾個常見的 CSS 屬性及其在 style 對象中對應(yīng)的屬性名聘惦。

CSS屬性 JavaScript屬性
background-image style.backgroundImage
color style.color
display style.display
font-family style.fontFamily

??多數(shù)情況下,都可以通過簡單地轉(zhuǎn)換屬性名的格式來實現(xiàn)轉(zhuǎn)換儒恋。其中一個不能直接轉(zhuǎn)換的 CSS 屬性就是 float善绎。由于 float 是 JavaScript 中的保留字,因此不能用作屬性名诫尽。
??“DOM2 級樣式”規(guī)范規(guī)定樣式對象上相應(yīng)的屬性名應(yīng)該是 cssFloat禀酱;Firefox、Safari牧嫉、Opera 和 Chrome 都支持這個屬性剂跟,而 IE
支持的則是 styleFloat。
??只要取得一個有效的 DOM 元素的引用酣藻,就可以隨時使用 JavaScript 為其設(shè)置樣式曹洽。以下是幾個例子。

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

// 設(shè)置背景顏色
myDiv.style.backgroundColor = "red";

// 改變大小
myDiv.style.width = "100px";
myDiv.style.height = "200px";

// 指定邊框
myDiv.style.border = "1px solid black";

??在以這種方式改變樣式時辽剧,元素的外觀會自動被更新衣洁。

??在標(biāo)準(zhǔn)模式下,所有度量值都必須指定一個度量單位抖仅。
??在混雜模式下坊夫,可以將 style.width 設(shè)置為"20"砖第,瀏覽器會假設(shè)它是"20px";
??但在標(biāo)準(zhǔn)模式下环凿,將 style.width 設(shè)置為"20"會導(dǎo)致被忽略——因為沒有度量單位梧兼。在實踐中,最好始終都指定度量單位智听。

??通過 style 對象同樣可以取得在 style 特性中指定的樣式羽杰。以下面的 HTML 代碼為例。

<div id="myDiv" style="background-color:blue; width:10px; height:25px"></div>

??在 style 特性中指定的樣式信息可以通過下列代碼取得到推。

console.log(myDiv.style.backgroundColor); // "blue"
console.log(myDiv.style.width); // "10px"
console.log(myDiv.style.height); // "25px" 

??如果沒有為元素設(shè)置 style 特性考赛,那么 style 對象中可能會包含一些默認(rèn)的值,但這些值并不能準(zhǔn)確地反映該元素的樣式信息莉测。

1. DOM 樣式屬性和方法

??“DOM2 級樣式”規(guī)范還為 style 對象定義了一些屬性和方法颜骤。這些屬性和方法在提供元素的 style特性值的同時,也可以修改樣式捣卤。下面列出了這些屬性和方法忍抽。

  • cssText:如前所述,通過它能夠訪問到 style 特性中的 CSS 代碼董朝。
  • length:應(yīng)用給元素的 CSS 屬性的數(shù)量鸠项。
  • parentRule:表示 CSS 信息的 CSSRule 對象。
  • getPropertyCSSValue(propertyName):返回包含給定屬性值的 CSSValue 對象子姜。
  • getPropertyPriority(propertyName):如果給定的屬性使用了!important 設(shè)置祟绊,則返回 "important";否則哥捕,返回空字符串牧抽。
  • getPropertyValue(propertyName):返回給定屬性的字符串值。
  • item(index):返回給定位置的 CSS 屬性的名稱扭弧。
  • removeProperty(propertyName):從樣式中刪除給定屬性。
  • setProperty(propertyName, value, priority):將給定屬性設(shè)置為相應(yīng)的值记舆,并加上優(yōu)先權(quán)標(biāo)志("important"或者一個空字符串)鸽捻。

??通過cssText 屬性可以訪問 style特性中的 CSS 代碼。
??在讀取模式下泽腮,cssText 返回瀏覽器對style 特性中 CSS 代碼的內(nèi)部表示御蒲。
??在寫入模式下,賦給 cssText 的值會重寫整個 style 特性的值诊赊;也就是說厚满,以前通過 style 特性指定的樣式信息都將丟失。
??例如碧磅,如果通過 style 特性為元素設(shè)置了邊框碘箍,然后再以不包含邊框的規(guī)則重寫 cssText遵馆,那么就會抹去元素上的邊框。下面是使用 cssText 屬性的一個例子丰榴。

myDiv.style.cssText = "width: 25px; height: 100px; background-color: green";
console.log(myDiv.style.cssText);

??設(shè)置 cssText 是為元素應(yīng)用多項變化最快捷的方式货邓,因為可以一次性地應(yīng)用所有變化。

??設(shè)計 length 屬性的目的四濒,就是將其與 item() 方法配套使用换况,以便迭代在元素中定義的 CSS 屬性。
??在使用 length 和 item() 時盗蟆,style 對象實際上就相當(dāng)于一個集合戈二,都可以使用方括號語法來代替 item() 來取得給定位置的 CSS 屬性,如下面的例子所示喳资。

for (var i=0, len=myDiv.style.length; i < len; i++){
    console.log(myDiv.style[i]); // 或者 myDiv.style.item(i)
}

??無論是使用方括號語法還是使用 item() 方法矫废,都可以取得 CSS 屬性名("background-color",不是"backgroundColor")坐昙。然后顾翼,就可以在 getPropertyValue() 中使用取得的屬性名進(jìn)一步取得屬性的值,如下所示宏赘。

var prop, value, i, len;
for (i=0, len=myDiv.style.length; i < len; i++){
    prop = myDiv.style[i]; // 或者 myDiv.style.item(i)
    value = myDiv.style.getPropertyValue(prop);
    console.log(prop + " : " + value);
}

??getPropertyValue() 方法取得的始終都是 CSS 屬性值的字符串表示绒北。

??如果你需要更多信息,可以使用 getPropertyCSSValue() 方法察署,它返回一個包含兩個屬性的 CSSValue 對象闷游,這兩個屬性分別是:cssText 和 cssValueType。其中贴汪,cssText 屬性的值與 getPropertyValue()返回的值相同脐往,而 cssValueType 屬性則是一個數(shù)值常量,表示值的類型:0 表示繼承的值扳埂,1 表示基本的值业簿,2 表示值列表,3 表示自定義的值阳懂。以下代碼既輸出 CSS 屬性值梅尤,也輸出值的類型。

var prop, value, i, len;
for (i=0, len=myDiv.style.length; i < len; i++){
    prop = myDiv.style[i]; // 或者 myDiv.style.item(i)
    value = myDiv.style.getPropertyCSSValue(prop);
    console.log(prop + " : " + value.cssText + " (" + value.cssValueType + ")");
}

??在實際開發(fā)中岩调,getPropertyCSSValue() 使用得比 getPropertyValue() 少得多巷燥。
??IE9+、Safarie3+ 以及 Chrome 支持這個方法号枕。Firefox 7 及之前版本也提供這個訪問缰揪,但調(diào)用總返回 null。

??要從元素的樣式中移除某個 CSS 屬性葱淳,需要使用 removeProperty() 方法钝腺。使用這個方法移除一個屬性抛姑,意味著將會為該屬性應(yīng)用默認(rèn)的樣式(從其他樣式表經(jīng)層疊而來)。例如拍屑,要移除通過 style 特性設(shè)置的 border 屬性途戒,可以使用下面的代碼。

myDiv.style.removeProperty("border");

??在不確定某個給定的 CSS 屬性擁有什么默認(rèn)值的情況下僵驰,就可以使用這個方法喷斋。只要移除相應(yīng)的屬性,就可以為元素應(yīng)用默認(rèn)值蒜茴。

2. 計算的樣式

??雖然 style 對象能夠提供支持 style 特性的任何元素的樣式信息星爪,但它不包含那些從其他樣式表層疊而來并影響到當(dāng)前元素的樣式信息。
??“DOM2 級樣式”增強(qiáng)了 document.defaultView粉私,提供了getComputedStyle() 方法顽腾。
??這個方法接受兩個參數(shù):要取得計算樣式的元素和一個偽元素字符串(例如":after")。如果不需要偽元素信息诺核,第二個參數(shù)可以是 null抄肖。
??getComputedStyle()方法返回一個 CSSStyleDeclaration 對象(與 style 屬性的類型相同),其中包含當(dāng)前元素的所有計算的樣式窖杀。以下面這個 HTML 頁面為例漓摩。

<!DOCTYPE html>
<html>
<head>
    <title>Computed Styles Example</title>
    <style type="text/css">
        #myDiv {
            background-color: blue;
            width: 100px;
            height: 200px;
        }
    </style>
</head>
<body>
    <div id="myDiv" style="background-color: red; border: 1px solid black"></div>
</body>
</html>

??應(yīng)用給這個例子中<div>元素的樣式一方面來自嵌入式樣式表(<style>元素中的樣式),另一方面來自其 style 特性入客。但是管毙,style 特性中設(shè)置了 backgroundColor 和 border,沒有設(shè)置 width 和 height桌硫,后者是通過樣式表規(guī)則應(yīng)用的夭咬。以下代碼可以取得這個元素計算后的樣式。

var myDiv = document.getElementById("myDiv");
var computedStyle = document.defaultView.getComputedStyle(myDiv, null);

console.log(computedStyle.backgroundColor); // "red"
console.log(computedStyle.width); // "100px"
console.log(computedStyle.height); // "200px"
console.log(computedStyle.border); // 在某些瀏覽器中是"1px solid black"

??在這個元素計算后的樣式中铆隘,背景顏色的值是"red"卓舵,寬度值是"100px",高度值是"200px"膀钠。
??我們注意到掏湾,背景顏色不是"blue",因為這個樣式在自身的 style 特性中已經(jīng)被覆蓋了托修。邊框?qū)傩钥赡軙部赡懿粫祷貥邮奖碇袑嶋H的 border 規(guī)則(Opera 會返回忘巧,但其他瀏覽器不會)恒界。
??存在這個差別的原因是不同瀏覽器解釋綜合(rollup)屬性(如 border)的方式不同睦刃,因為設(shè)置這種屬性實際上會涉及很多其他屬性。在設(shè)置 border 時十酣,實際上是設(shè)置了四個邊的邊框?qū)挾壬尽㈩伾食ぁ邮綄傩裕?border-left-width 、 border-top-color 兴泥、 border-bottom-style 工育, 等 等 )。
??因 此 搓彻, 即 使 computedStyle.border 不會在所有瀏覽器中都返回值如绸,但 computedStyle.borderLeftWidth 會返回值。

??需要注意的是旭贬,即使有些瀏覽器支持這種功能怔接,但表示值的方式可能會有所區(qū)別。例如稀轨,F(xiàn)irefox 和 Safari 會將所有顏色轉(zhuǎn)換成 RGB 格式(例如紅色是 rgb(255,0,0))扼脐。
??因此,在使用 getComputedStyle()方法時奋刽,最好多在幾種瀏覽器中測試一下瓦侮。

??IE 不支持 getComputedStyle() 方法,但它有一種類似的概念佣谐。在 IE 中肚吏,每個具有 style 屬性的元素還有一個 currentStyle 屬性。這個屬性是CSSStyleDeclaration 的實例台谍,包含當(dāng)前元素全部計算后的樣式须喂。取得這些樣式的方式也差不多,如下面的例子所示趁蕊。

var myDiv = document.getElementById("myDiv");
var computedStyle = myDiv.currentStyle;

console.log(computedStyle.backgroundColor); // "red"
console.log(computedStyle.width); // "100px"
console.log(computedStyle.height); // "200px"
console.log(computedStyle.border); // undefined

??與 DOM 版本的方式一樣坞生,IE 也沒有返回 border 樣式,因為這是一個綜合屬性掷伙。

??無論在哪個瀏覽器中是己,最重要的一條是要記住所有計算的樣式都是只讀的;不能修改計算后樣式對象中的 CSS 屬性任柜。
??此外卒废,計算后的樣式也包含屬于瀏覽器內(nèi)部樣式表的樣式信息,因此任何具有默認(rèn)值的 CSS 屬性都會表現(xiàn)在計算后的樣式中宙地。
??例如摔认,所有瀏覽器中的 visibility 屬性都有一個默認(rèn)值,但這個值會因?qū)崿F(xiàn)而異宅粥。在默認(rèn)情況下参袱,有的瀏覽器將 visibility 屬性設(shè)置為"visible",而有的瀏覽器則將其設(shè)置為"inherit"。換句話說抹蚀,不能指望某個 CSS 屬性的默認(rèn)值在不同瀏覽器中是相同的剿牺。如果你需要元素具有某個特定的默認(rèn)值,應(yīng)該手工在樣式表中指定該值环壤。

2.2晒来、操作樣式表

??CSSStyleSheet 類型表示的是樣式表,包括通過 <link> 元素包含的樣式表和在<style>元素中定義的樣式表郑现。
??這兩個元素本身分別是由 HTMLLinkElement 和 HTMLStyleElement 類型表示的湃崩。但是,CSSStyleSheet 類型相對更加通用一些接箫,它只表示樣式表竹习,而不管這些樣式表在 HTML 中是如何定義的。
??此外列牺,上述兩個針對元素的類型允許修改 HTML 特性整陌,但 CSSStyleSheet 對象則是一套只讀的接口(有一個屬性例外)。使用下面的代碼可以確定瀏覽器是否支持 DOM2 級樣式表瞎领。

var supportsDOM2StyleSheets =
                     document.implementation.hasFeature("StyleSheets", "2.0");

??CSSStyleSheet 繼承自 StyleSheet泌辫,后者可以作為一個基礎(chǔ)接口來定義非 CSS 樣式表。從StyleSheet 接口繼承而來的屬性如下九默。

  • disabled:表示樣式表是否被禁用的布爾值震放。這個屬性是可讀/寫的,將這個值設(shè)置為 true 可以禁用樣式表驼修。
  • href:如果樣式表是通過<link>包含的殿遂,則是樣式表的 URL;否則乙各,是 null墨礁。
  • media:當(dāng)前樣式表支持的所有媒體類型的集合。與所有 DOM 集合一樣耳峦,這個集合也有一個 length 屬性和一個 item() 方法恩静。也可以使用方括號語法取得集合中特定的項。如果集合是空列表蹲坷,表示樣式表適用于所有媒體驶乾。在 IE 中,media 是一個反映<link>和<style>元素 media 特性值的字符串循签。
  • ownerNode:指向擁有當(dāng)前樣式表的節(jié)點的指針级乐,樣式表可能是在 HTML 中通過<link>或<style/>引入的(在 XML 中可能是通過處理指令引入的)。如果當(dāng)前樣式表是其他樣式表通過 @import 導(dǎo)入的县匠,則這個屬性值為 null风科。IE 不支持這個屬性罕扎。
  • parentStyleSheet:在當(dāng)前樣式表是通過 @import 導(dǎo)入的情況下,這個屬性是一個指向?qū)胨臉邮奖淼闹羔槨?/li>
  • title:ownerNode 中 title 屬性的值丐重。
  • type:表示樣式表類型的字符串。對 CSS 樣式表而言杆查,這個字符是"type/css"扮惦。

??除 了 disabled 屬性之外,其他屬性都是只讀的亲桦。在支持以上所有這些屬性的基礎(chǔ)上崖蜜,CSSStyleSheet 類型還支持下列屬性和方法:

  • cssRules:樣式表中包含的樣式規(guī)則的集合。IE 不支持這個屬性客峭,但有一個類似的 rules 屬性豫领。
  • ownerRule:如果樣式表是通過@import 導(dǎo)入的,這個屬性就是一個指針舔琅,指向表示導(dǎo)入的規(guī)則等恐;否則,值為 null备蚓。IE 不支持這個屬性课蔬。
  • deleteRule(index):刪除 cssRules 集合中指定位置的規(guī)則。IE 不支持這個方法郊尝,但支持一個類似的 removeRule() 方法二跋。
  • insertRule(rule, index):向 cssRules 集合中指定的位置插入 rule 字符串。IE 不支持這個方法流昏,但支持一個類似的 addRule() 方法扎即。

??應(yīng)用于文檔的所有樣式表是通過 document.styleSheets 集合來表示的。通過這個集合的 length 屬性可以獲知文檔中樣式表的數(shù)量况凉,而通過方括號語法或 item() 方法可以訪問每一個樣式表谚鄙。示例:

var sheet = null;
for (var i=0, len=document.styleSheets.length; i < len; i++){ 
    sheet = document.styleSheets[i];
    console.log(sheet.href);
}

??以上代碼可以輸出文檔中使用的每一個樣式表的 href 屬性(<style>元素包含的樣式表沒有 href 屬性)。

??不同瀏覽器的 document.styleSheets 返回的樣式表也不同刁绒。所有瀏覽器都會包含<style>元素和 rel 特性被設(shè)置為"stylesheet"的<link>元素引入的樣式表襟锐。
??IE 和 Opera 也包含 rel 特性被設(shè)置為"alternate stylesheet"的<link>元素引入的樣式表。
??也可以直接通過<link>或<style>元素取得 CSSStyleSheet 對象膛锭。DOM 規(guī)定了一個包含 CSSStyleSheet 對象的屬性粮坞,名叫 sheet;除了 IE初狰,其他瀏覽器都支持這個屬性莫杈。IE 支持的是 styleSheet 屬性。要想在不同瀏覽器中都能取得樣式表對象奢入,可以使用下列代碼筝闹。

function getStyleSheet(element){
    return element.sheet || element.styleSheet;
}

// 取得第一個<link/>元素引入的樣式表
var link = document.getElementsByTagName("link")[0];
var sheet = getStylesheet(link);

??這里的 getStyleSheet() 返回的樣式表對象與 document.styleSheets 集合中的樣式表對象相同媳叨。

1. CSS 規(guī)則

??CSSRule 對象表示樣式表中的每一條規(guī)則。實際上关顷,CSSRule 是一個供其他多種類型繼承的基類型糊秆,其中最常見的就是 CSSStyleRule 類型,表示樣式信息(其他規(guī)則還有 @import议双、@font-face痘番、@page 和 @charset,但這些規(guī)則很少有必要通過腳本來訪問)平痰。
??CSSStyleRule 對象還包含下列屬性汞舱。

  • cssText:返回整條規(guī)則對應(yīng)的文本。由于瀏覽器對樣式表的內(nèi)部處理方式不同宗雇,返回的文本可能會與樣式表中實際的文本不一樣昂芜;Safari 始終都會將文本轉(zhuǎn)換成全部小寫。IE 不支持這個屬性赔蒲。
  • parentRule:如果當(dāng)前規(guī)則是導(dǎo)入的規(guī)則泌神,這個屬性引用的就是導(dǎo)入規(guī)則;否則舞虱,這個值為 null腻扇。IE 不支持這個屬性。
  • parentStyleSheet:當(dāng)前規(guī)則所屬的樣式表砾嫉。IE 不支持這個屬性幼苛。
  • selectorText:返回當(dāng)前規(guī)則的選擇符文本。由于瀏覽器對樣式表的內(nèi)部處理方式不同焕刮,返回的文本可能會與樣式表中實際的文本不一樣(例如:Safari 3 之前的版本始終會將文本轉(zhuǎn)換成全部小寫)舶沿。在 Firefox、Safari配并、Chrome 和 IE 中這個屬性是只讀的括荡。Opera 允許修改 selectorText。
  • style:一個 CSSStyleDeclaration 對象溉旋,可以通過它設(shè)置和取得規(guī)則中特定的樣式值畸冲。
  • type:表示規(guī)則類型的常量值。對于樣式規(guī)則观腊,這個值是 1邑闲。IE 不支持這個屬性。

??其中三個最常用的屬性是 cssText梧油、selectorText 和 style苫耸。cssText 屬性與 style.cssText 屬性類似,但并不相同儡陨。前者包含選擇符文本和圍繞樣式信息的花括號褪子,后者指包含樣式信息(類似于元素的 style.cssText)量淌。此外,cssText 是只讀的嫌褪,而 style.cssText 也可以被重寫呀枢。
??大多數(shù)情況下,僅使用 style 屬性就可以滿足所有操作樣式規(guī)則的需求了笼痛。這個對象就像每個元素上的 style 屬性一樣裙秋,可以通過它讀取和修改規(guī)則中的樣式信息。以下面的 CSS 規(guī)則為例晃痴。

div.box {
    background-color: blue;
    width: 100px;
    height: 200px;
} 

??假設(shè)這條規(guī)則位于頁面中的第一個樣式表中,而且這個樣式表中只有這一條樣式規(guī)則财忽,那么通過下列代碼可以取得這條規(guī)則的各種信息倘核。

var sheet = document.styleSheets[0];
var rules = sheet.cssRules || sheet.rules; // 取得規(guī)則列表
var rule = rules[0]; // 取得第一條規(guī)則
console.log(rule.selectorText); // "div.box"
console.log(rule.style.cssText); // 完整的 CSS 代碼
console.log(rule.style.backgroundColor); // "blue"
console.log(rule.style.width); // "100px"
console.log(rule.style.height); // "200px"

??使用這種方式,可以像確定元素的行內(nèi)樣式信息一樣即彪,確定與規(guī)則相關(guān)的樣式信息紧唱。與使用元素的方式一樣,在這種方式下也可以修改樣式信息隶校,如下面的例子所示漏益。

var sheet = document.styleSheets[0];
var rules = sheet.cssRules || sheet.rules; // 取得規(guī)則列表
var rule = rules[0]; // 取得第一條規(guī)則
rule.style.backgroundColor = "red"

??必須要注意的是,以這種方式修改規(guī)則會影響頁面中適用于該規(guī)則的所有元素深胳。換句話說绰疤,如果有兩個帶有 box 類的<div>元素,那么這兩個元素都會應(yīng)用修改后的樣式舞终。

2. 創(chuàng)建規(guī)則

??DOM 規(guī)定轻庆,要向現(xiàn)有樣式表中添加新規(guī)則,需要使用 insertRule() 方法敛劝。這個方法接受兩個參數(shù):規(guī)則文本和表示在哪里插入規(guī)則的索引余爆。示例:

sheet.insertRule("body { background-color: silver }", 0); // DOM 方法

??上述例子插入的規(guī)則會改變元素的背景顏色。插入的規(guī)則將成為樣式表中的第一條規(guī)則(插入到了位置 0)—— 規(guī)則的次序在確定層疊之后應(yīng)用到文檔的規(guī)則時至關(guān)重要夸盟。Firefox蛾方、Safari、Opera 和 Chrome 都支持 insertRule() 方法上陕。
??IE8 及更早版本支持一個類似的方法桩砰,名叫 addRule(),也接收兩必選參數(shù):選擇符文本和 CSS 樣式信息释簿;一個可選參數(shù):插入規(guī)則的位置五芝。在 IE 中插入與前面例子相同的規(guī)則,可使用如下代碼辕万。

sheet.addRule("body", "background-color: silver", 0); // 僅對 IE 有效

??有關(guān)這個方法的規(guī)定中說枢步,最多可以使用 addRule() 添加 4 095 條樣式規(guī)則沉删。超出這個上限的調(diào)用將會導(dǎo)致錯誤。
??要以跨瀏覽器的方式向樣式表中插入規(guī)則醉途,可以使用下面的函數(shù)矾瑰。這個函數(shù)接受 4 個參數(shù):要向其中添加規(guī)則的樣式表以及與 addRule() 相同的 3 個參數(shù),如下所示隘擎。

function insertRule(sheet, selectorText, cssText, position){
    if (sheet.insertRule){
        sheet.insertRule(selectorText + "{" + cssText + "}", position);
    } else if (sheet.addRule){
        sheet.addRule(selectorText, cssText, position);
    }
}

??下面是調(diào)用這個函數(shù)的示例代碼殴穴。

insertRule(document.styleSheets[0], "body", "background-color: silver", 0);

??雖然可以像這樣來添加規(guī)則,但隨著要添加規(guī)則的增多货葬,這種方法就會變得非常繁瑣采幌。因此,如果要添加的規(guī)則非常多震桶,我們建議還是采用動態(tài)加載樣式表的技術(shù)休傍。

3. 刪除規(guī)則

??從樣式表中刪除規(guī)則的方法是 deleteRule()锹淌,這個方法接受一個參數(shù):要刪除的規(guī)則的位置凭疮。
??例如舰绘,要刪除樣式表中的第一條規(guī)則穿撮,可以使用以下代碼达传。

sheet.deleteRule(0); // DOM 方法

??IE 支持的類似方法叫 removeRule()摹恨,使用方法相同铃岔,如下所示:

sheet.removeRule(0); // 僅對 IE 有效

??下面是一個能夠跨瀏覽器刪除規(guī)則的函數(shù)牲距。第一個參數(shù)是要操作的樣式表江咳,第二個參數(shù)是要刪除的規(guī)則的索引逢净。

function deleteRule(sheet, index){
    if (sheet.deleteRule){
        sheet.deleteRule(index);
    } else if (sheet.removeRule){
        sheet.removeRule(index);
    }
}

??調(diào)用這個函數(shù)的方式如下。

deleteRule(document.styleSheets[0], 0);

??與添加規(guī)則相似歼指,刪除規(guī)則也不是實際 Web 開發(fā)中常見的做法汹胃。考慮到刪除規(guī)則可能會影響 CSS 層疊的效果东臀,因此請大家慎重使用着饥。

2.3、元素大小

??本節(jié)介紹的屬性和方法并不屬于“DOM2級樣式”規(guī)范惰赋,但卻與 HTML 元素的樣式息息相關(guān)宰掉。DOM 中沒有規(guī)定如何確定頁面中元素的大小。IE 為此率先引入了一些屬性赁濒,以便開發(fā)人員使用轨奄。目前,所有主要的瀏覽器都已經(jīng)支持這些屬性拒炎。

1. 偏移量

??首先要介紹的屬性涉及偏移量(offset dimension)挪拟,包括元素在屏幕上占用的所有可見的空間。元素的可見大小尤其高度击你、寬度決定玉组,包括所有內(nèi)邊距谎柄、滾動條和邊框大小(注意惯雳,不包括外邊距)朝巫。通過下列 4 個屬性可以取得元素的偏移量。

  • offsetHeight:元素在垂直方向上占用的空間大小石景,以像素計劈猿。包括元素的高度、(可見的)水平滾動條的高度潮孽、上邊框高度和下邊框高度揪荣。
  • offsetWidth:元素在水平方向上占用的空間大小,以像素計往史。包括元素的高度仗颈、(可見的)垂直滾動條的寬度、左邊框?qū)挾群陀羞吙驅(qū)挾取?/li>
  • offsetLeft:元素的左外邊框至包含元素的左內(nèi)邊框之間的像素距離怠堪。
  • offsetTop:元素的上外邊框至包含元素的上內(nèi)邊框之間的像素距離揽乱。

??其中名眉,offsetLeft 和 offsetTop 屬性與包含元素有關(guān)粟矿,包含元素的引用保存在 offsetParent 屬性中。offsetParent 屬性不一定與 parentNode 的值相等损拢。
??例如陌粹,<td>元素的 offsetParent 是作為其祖先元素的<table>元素,因為<table>是在 DOM 層次中距<td>最近的一個具有大小的元素福压。
??下圖形象地展示了上面幾個屬性表示的不同大小掏秩。

??要想知道某個元素在頁面上的偏移量,將這個元素的 offsetLeft 和 offsetTop 與其 offsetParent 的相同屬性相加荆姆,如此循環(huán)直至根元素蒙幻,就可以得到一個基本準(zhǔn)確的值。以下兩個函數(shù)就可以用于分別取得元素的左和上偏移量胆筒。

// 元素的左偏移量
function getElementLeft(element){
    var actualLeft = element.offsetLeft;
    var current = element.offsetParent;
    while (current !== null){
        actualLeft += current.offsetLeft;
        current = current.offsetParent;
    }
    return actualLeft;
}

// 元素的上偏移量
function getElementTop(element){
    var actualTop = element.offsetTop;
    var current = element.offsetParent;
    while (current !== null){
        actualTop += current. offsetTop;
        current = current.offsetParent;
    }
    return actualTop;
} 

??這兩個函數(shù)利用 offsetParent 屬性在 DOM 層次中逐級向上回溯邮破,將每個層次中的偏移量屬性合計到一塊。對于簡單的 CSS 布局的頁面仆救,這兩函數(shù)可以得到非常精確的結(jié)果抒和。對于使用表格和內(nèi)嵌框架布局的頁面,由于不同瀏覽器實現(xiàn)這些元素的方式不同彤蔽,因此得到的值就不太精確了摧莽。
??一般來說,頁面中的所有元素都會被包含在幾個<div>元素中顿痪,而這些<div>元素的 offsetParent 又是 <body>元素镊辕,所以 getElementLeft() 與 getElementTop() 會返回與 offsetLeft 和 offsetTop 相同的值油够。

??所有這些偏移量屬性都是只讀的,而且每次訪問它們都需要重新計算丑蛤。因此叠聋,應(yīng)該盡量避免重復(fù)訪問這些屬性;如果需要重復(fù)使用其中某些屬性的值受裹,可以將它們保存在局部變量中碌补,以提高性能。

2. 客戶區(qū)大小

??元素的客戶區(qū)大忻奕摹(client dimension)厦章,指的是元素內(nèi)容及其內(nèi)邊距所占據(jù)的空間大小。有關(guān)客戶區(qū)大小的屬性有兩個:clientWidth 和 clientHeight照藻。
??其中袜啃,clientWidth 屬性是元素內(nèi)容區(qū)寬度加上左右內(nèi)邊距寬度;clientHeight 屬性是元素內(nèi)容區(qū)高度加上上下內(nèi)邊距高度幸缕。
??下圖形象地說明了這些屬性表示的大小群发。

??從字面上看,客戶區(qū)大小就是元素內(nèi)部的空間大小发乔,因此滾動條占用的空間不計算在內(nèi)熟妓。最常用到這些屬性的情況,就是像確定瀏覽器視口大小的時候栏尚。
??如下面的例子所示起愈,要確定瀏覽器視口大小,可以使用document.documentElement 或 document.body(在 IE7 之前的版本中)的 clientWidth 和 clientHeight译仗。

function getViewport(){
    if (document.compatMode == "BackCompat"){
        return {
            width: document.body.clientWidth,
            height: document.body.clientHeight
        };
    } else {
        return {
            width: document.documentElement.clientWidth,
            height: document.documentElement.clientHeight
        };
    }
}

??這個函數(shù)首先檢查 document.compatMode 屬性抬虽,以確定瀏覽器是否運行在混雜模式。Safari 3.1 之前的版本不支持這個屬性纵菌,因此就會自動執(zhí)行 else 語句阐污。Chrome、Opera 和 Firefox 大多數(shù)情況下都運行在標(biāo)準(zhǔn)模式下咱圆,因此它們也會前進(jìn)到 else 語句笛辟。
??這個函數(shù)會返回一個對象,包含兩個屬性:width 和 height闷堡;表示瀏覽器視口(<html>或<body>元素)的大小隘膘。

與偏移量相似,客戶區(qū)大小也是只讀的杠览,也是每次訪問都要重新計算的弯菊。

3. 滾動大小

??最后要介紹的是滾動大小(scroll dimension),指的是包含滾動內(nèi)容的元素的大小管钳。有些元素(例如<html>元素)钦铁,即使沒有執(zhí)行任何代碼也能自動地添加滾動條;但另外一些元素才漆,則需要通過 CSS 的 overflow 屬性進(jìn)行設(shè)置才能滾動牛曹。以下是 4 個與滾動大小相關(guān)的屬性。

  • scrollHeight:在沒有滾動條的情況下醇滥,元素內(nèi)容的總高度黎比。
  • scrollWidth:在沒有滾動條的情況下,元素內(nèi)容的總寬度鸳玩。
  • scrollLeft:被隱藏在內(nèi)容區(qū)域左側(cè)的像素數(shù)阅虫。通過設(shè)置這個屬性可以改變元素的滾動位置。
  • scrollTop:被隱藏在內(nèi)容區(qū)域上方的像素數(shù)不跟。通過設(shè)置這個屬性可以改變元素的滾動位置颓帝。

??下圖展示了這些屬性代表的大小。

??scrollWidth 和 scrollHeight 主要用于確定元素內(nèi)容的實際大小窝革。例如购城,通常認(rèn)為<html>元素是在 Web 瀏覽器的視口中滾動的元素(IE6 之前版本運行在混雜模式下時是<body>元素)。因此虐译,帶有垂直滾動條的頁面總高度就是 document.documentElement.scrollHeight瘪板。
??對于不包含滾動條的頁面而言, scrollWidth 和 scrollHeight 與 clientWidth 和 clientHeight 之間的關(guān)系并不十分清晰菱蔬。在這種情況下篷帅,基于 document.documentElement 查看這些屬性會在不同瀏覽器間發(fā)現(xiàn)一些不一致性問題史侣,如下所述拴泌。

  • Firefox 中這兩組屬性始終都是相等的,但大小代表的是文檔內(nèi)容區(qū)域的實際尺寸惊橱,而非視口的尺寸蚪腐。
  • Opera、Safari 3.1 及更高版本税朴、Chrome 中的這兩組屬性是有差別的回季,其中 scrollWidth 和 scrollHeight 等于視口大小,而 clientWidth 和 clientHeight 等于文檔內(nèi)容區(qū)域的大小正林。
  • IE(在標(biāo)準(zhǔn)模式)中的這兩組屬性不相等泡一,其中 scrollWidth 和 scrollHeight 等于文檔內(nèi)容區(qū)域的大小,而 clientWidth 和 clientHeight 等于視口大小觅廓。

??在確定文檔的總高度時(包括基于視口的最小高度時)鼻忠,必須取得 scrollWidth/clientWidth 和 scrollHeight/clientHeight 中的最大值,才能保證在跨瀏覽器的環(huán)境下得到精確的結(jié)果杈绸。下面就是這樣一個例子帖蔓。

var docHeight = Math.max(document.documentElement.scrollHeight,
                         document.documentElement.clientHeight);

var docWidth = Math.max(document.documentElement.scrollWidth,
                        document.documentElement.clientWidth);

??注意矮瘟,對于運行在混雜模式下的 IE,則需要用 document.body 代替 document.documentElement塑娇。
??通過 scrollLeft 和 scrollTop 屬性既可以確定元素當(dāng)前滾動的狀態(tài)澈侠,也可以設(shè)置元素的滾動位置。
??在元素尚未被滾動時埋酬,這兩個屬性的值都等于 0哨啃。
??如果元素被垂直滾動了,那么 scrollTop 的值會大于 0写妥,且表示元素上方不可見內(nèi)容的像素高度棘催。
??如果元素被水平滾動了,那么 scrollLeft 的值會大于 0耳标,且表示元素左側(cè)不可見內(nèi)容的像素寬度醇坝。
??這兩個屬性都是可以設(shè)置的,因此將元素的 scrollLeft 和 scrollTop 設(shè)置為 0次坡,就可以重置元素的滾動位置呼猪。下面這個函數(shù)會檢測元素是否位于頂部,如果不是就將其回滾到頂部砸琅。

function scrollToTop(element){
    if (element.scrollTop != 0){
        element.scrollTop = 0;
    }
}

??這個函數(shù)既取得了 scrollTop 的值宋距,也設(shè)置了它的值。

4. 確定元素大小

??IE症脂、Firefox 3+谚赎、Safari 4+、Opera 9.5 及 Chrome 為每個元素都提供了一個 getBoundingClientRect() 方法诱篷。這個方法返回會一個矩形對象壶唤,包含 4 個屬性:left、top棕所、right 和 bottom闸盔。
??這些屬性給出了元素在頁面中相對于視口的位置。但是琳省,瀏覽器的實現(xiàn)稍有不同迎吵。IE8 及更早版本認(rèn)為文檔的左上角坐標(biāo)是 (2, 2),而其他瀏覽器包括 IE9 則將傳統(tǒng)的 (0, 0) 作為起點坐標(biāo)针贬。因此击费,就需要在一開始檢查一下位于 (0, 0) 處的元素的位置,在 IE8 及更早版本中桦他,會返回 (2, 2)蔫巩,而在其他瀏覽器中會返回 (0, 0)。來看下面的函數(shù):

function getBoundingClientRect(element){
    if (typeof arguments.callee.offset != "number"){
        var scrollTop = document.documentElement.scrollTop;
        var temp = document.createElement("div");
        temp.style.cssText = "position:absolute;left:0;top:0;";
        document.body.appendChild(temp);
        arguments.callee.offset = -temp.getBoundingClientRect().top - scrollTop;
        document.body.removeChild(temp);
        temp = null;
    }

    var rect = element.getBoundingClientRect();
    var offset = arguments.callee.offset;

    return {
        left: rect.left + offset,
        right: rect.right + offset,
        top: rect.top + offset,
        bottom: rect.bottom + offset
    };
}

??上述函數(shù)使用了它自身的屬性來確定是否要對坐標(biāo)進(jìn)行調(diào)整。
??第一步是檢測屬性是否有定義批幌,如果沒有就定義一個础锐。最終的 offset 會被設(shè)置為新元素上坐標(biāo)的負(fù)值,實際上就是在 IE 中設(shè)置為 -2荧缘,在 Firefox 和 Opera 中設(shè)置為 -0皆警。為此,需要創(chuàng)建一個臨時的元素截粗,將其位置設(shè)置在(0,0)信姓,然后再調(diào)用其 getBoundingClientRect()。而之所以要減去視口的 scrollTop绸罗,是為了防止調(diào)用這個函數(shù)時窗口被滾動了意推。這樣編寫代碼,就無需每次調(diào)用這個函數(shù)都執(zhí)行兩次getBoundingClientRect() 了珊蟀。接下來菊值,再在傳入的元素上調(diào)用這個方法并基于新的計算公式創(chuàng)建一個對象。
??對于不支持 getBoundingClientRect() 的瀏覽器育灸,可以通過其他手段取得相同的信息腻窒。一般來說,right 和 left 的差值與 offsetWidth 的值相等磅崭,而 bottom 和 top 的差值與 offsetHeight 相等儿子。而且,left 和 top 屬性大致等于使用本章前面定義的 getElementLeft() 和 getElementTop() 函數(shù)取得的值砸喻。綜合上述柔逼,就可以創(chuàng)建出下面這個跨瀏覽器的函數(shù):

function getBoundingClientRect(element){

    var scrollTop = document.documentElement.scrollTop;
    var scrollLeft = document.documentElement.scrollLeft;

    if (element.getBoundingClientRect){
        if (typeof arguments.callee.offset != "number"){
            var temp = document.createElement("div");
            temp.style.cssText = "position:absolute;left:0;top:0;";
            document.body.appendChild(temp);
            arguments.callee.offset = -temp.getBoundingClientRect().top - scrollTop;
            document.body.removeChild(temp); 
           temp = null;
        }

        var rect = element.getBoundingClientRect();
        var offset = arguments.callee.offset;

        return {
            left: rect.left + offset,
            right: rect.right + offset,
            top: rect.top + offset,
            bottom: rect.bottom + offset
        };
    } else {

        var actualLeft = getElementLeft(element);
        var actualTop = getElementTop(element);

        return {
            left: actualLeft - scrollLeft,
            right: actualLeft + element.offsetWidth - scrollLeft,
            top: actualTop - scrollTop,
            bottom: actualTop + element.offsetHeight - scrollTop
        }
    }
} 

??這個函數(shù)在 getBoundingClientRect() 有效時,就是用這個原生方法割岛,而在這個方法無效時則使用默認(rèn)的計算公式愉适。在某些情況下,這個函數(shù)返回的值可能會有所不同蜂桶,例如使用表格布局或使用滾動元素的情況下儡毕。

由于這里使用了 arguments.callee也切,所以這個方法不能在嚴(yán)格模式下使用扑媚。

3、遍歷

??“DOM 2 級遍歷和范圍”模塊定義了兩個用于輔助完成順序遍歷 DOM 結(jié)構(gòu)的類型:NodeIterator 和 TreeWalker雷恃。這兩個類型能夠基于給定的起點對 DOM 結(jié)構(gòu)執(zhí)行深度優(yōu)先(depth-first)的遍歷操作疆股。
??在與 DOM 兼容的瀏覽器中(Firefox 1 及更高版本、Safari 1.3 及更高版本倒槐、Opera 7.6 及更高版本旬痹、Chrome 0.2 及更高版本),都可以訪問到這些類型的對象。IE 不支持 DOM 遍歷两残。使用下列代碼可以檢測瀏覽器對 DOM2 級遍歷能力的支持情況永毅。

var supportsTraversals = document.implementation.hasFeature("Traversal", "2.0");
var supportsNodeIterator = (typeof document.createNodeIterator == "function");
var supportsTreeWalker = (typeof document.createTreeWalker == "function"); 

??如前所述,DOM 遍歷時深度優(yōu)先的 DOM 結(jié)構(gòu)遍歷人弓,也就是說沼死,移動的方向至少有兩個(取決于使用的遍歷類型)。遍歷以給定節(jié)點為根崔赌,不可能向上超出 DOM 樹的根節(jié)點意蛀。以下面的 HTML 頁面為例:

<!DOCTYPE html>
<html>
    <head>
        <title>Example</title>
    </head>
    <body>
        <p><b>Hello</b> world!</p>
    </body>
</html> 

??下圖展示了上述頁面的 DOM 樹。

??任何節(jié)點都可以作為遍歷的根節(jié)點健芭。如果假設(shè)<body>元素為根節(jié)點县钥,那么遍歷的第一步就是訪問<p>元素,然后再訪問同位<body>元素后代的兩個文本節(jié)點慈迈。不過若贮,這次遍歷永遠(yuǎn)不會到達(dá)<html>、<head>元素痒留,也不會到達(dá)不屬于<body>元素子樹的任何節(jié)點兜看。
??而以 document 為根節(jié)點的遍歷則可以訪問到文檔中的全部節(jié)點。下圖展示了對以 document 為根節(jié)點的 DOM 樹進(jìn)行深度優(yōu)先遍歷的先后順序狭瞎。

??從 document 開始依序向前细移,訪問的第一個節(jié)點是 document,訪問的最后一個節(jié)點是包含“world!”的文本節(jié)點熊锭。從文檔最后的文本節(jié)點開始弧轧,遍歷可以反向移動到 DOM 樹的頂端。此時碗殷,訪問的第一個節(jié)點是包含“hello”的文本節(jié)點精绎,訪問的最后一個節(jié)點是 document 節(jié)點。NodeIterator 和 TreeWalker 都以這種方式執(zhí)行遍歷锌妻。

NodeIterator

??NodeIterator 類型是兩者中比較簡單的一個代乃,可以使用document.createNodeIterator() 方法創(chuàng)建它的新實例。這個方法接受下列 4 個參數(shù)仿粹。

  • root:想要作為搜索起點的樹中的節(jié)點搁吓。
  • whatToShow:表示要訪問哪些節(jié)點的數(shù)字代碼。
  • filter:是一個 NodeFilter 對象吭历,或者一個表示應(yīng)該接受還是拒絕某種特定節(jié)點的函數(shù)堕仔。
  • entityReferenceExpansion:布爾值,表示是否要擴(kuò)展實體引用晌区。這個參數(shù)在 HTML 頁面中沒有用摩骨,因為其中的實體引用不能擴(kuò)展通贞。

??whatToShow 參數(shù)是一個位掩碼,通過應(yīng)用一或多個過濾器(filter)來確定要訪問哪些節(jié)點恼五。這個參數(shù)的值以常量形式在 NodeFilter 類型中定義昌罩,如下所示。

  • NodeFilter.SHOW_ALL:顯示所有類型的節(jié)點灾馒。
  • NodeFilter.SHOW_ELEMENT:顯示元素節(jié)點峡迷。
  • NodeFilter.SHOW_ATTRIBUTE:顯示特性節(jié)點。由于 DOM 結(jié)構(gòu)原因你虹,實際上不能使用這個值绘搞。
  • NodeFilter.SHOW_TEXT:顯示文本節(jié)點。
  • NodeFilter.SHOW_CDATA_SECTION:顯示 CDATA 節(jié)點傅物。對 HTML 頁面沒有用夯辖。
  • NodeFilter.SHOW_ENTITY_REFERENCE:顯示實體引用節(jié)點。對 HTML 頁面沒有用董饰。
  • NodeFilter.SHOW_ENTITYE:顯示實體節(jié)點蒿褂。對 HTML 頁面沒有用。
  • NodeFilter.SHOW_PROCESSING_INSTRUCTION:顯示處理指令節(jié)點卒暂。對 HTML 頁面沒有用啄栓。
  • NodeFilter.SHOW_COMMENT:顯示注釋節(jié)點。
  • NodeFilter.SHOW_DOCUMENT:顯示文檔節(jié)點也祠。
  • NodeFilter.SHOW_DOCUMENT_TYPE:顯示文檔類型節(jié)點昙楚。
  • NodeFilter.SHOW_DOCUMENT_FRAGMENT:顯示文檔片段節(jié)點。對 HTML 頁面沒有用诈嘿。
  • NodeFilter.SHOW_NOTATION:顯示符號節(jié)點堪旧。對 HTML 頁面沒有用。

??除了 NodeFilter.SHOW_ALL 之外奖亚,可以使用按位或操作符來組合多個選項淳梦,如下面的例子所示:

var whatToShow = NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT;

??可以通過 createNodeIterator() 方法的 filter 參數(shù)來指定自定義的 NodeFilter 對象,或者指定一個功能類似節(jié)點過濾器(node filter)的函數(shù)昔字。
??每個 NodeFilter 對象只有一個方法爆袍,即 acceptNode()华糖;如果應(yīng)該訪問給定的節(jié)點缸兔,該方法返回 NodeFilter.FILTER_ACCEPT,如果不應(yīng)該訪問給定的節(jié)點置济,該方法返回 NodeFilter.FILTER_SKIP所坯。
??由于 NodeFilter 是一個抽象的類型谆扎,因此不能直接創(chuàng)建它的實例。在必要時芹助,只要創(chuàng)建一個包含 acceptNode() 方法的對象堂湖,然后將這個對象傳入 createNodeIterator() 中即可。例如状土,下列代碼展示了如何創(chuàng)建一個只顯示<p>元素的節(jié)點迭代器无蜂。

var filter = {
    acceptNode: function(node){
        return node.tagName.toLowerCase() == "p" ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
    }
};
var iterator = document.createNodeIterator(root, NodeFilter.SHOW_ELEMENT, filter, false); 

??第三個參數(shù)也可以是一個與 acceptNode() 方法類似的函數(shù),如下所示蒙谓。

var filter = function(node){
    return node.tagName.toLowerCase() == "p" ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
};
var iterator = document.createNodeIterator(root, NodeFilter.SHOW_ELEMENT, filter, false); 

??一般來說斥季,這就是在 JavaScript 中使用這個方法的形式,這種形式比較簡單累驮,而且也跟其他的 JavaScript 代碼很相似酣倾。如果不指定過濾器,那么應(yīng)該在第三個參數(shù)的位置上傳入 null谤专。
??下面的代碼創(chuàng)建了一個能夠訪問所有類型節(jié)點的簡單的 NodeIterator躁锡。


var iterator = document.createNodeIterator(document, NodeFilter.SHOW_ALL, null, false); 

??NodeIterator 類型的兩個主要方法是 nextNode() 和 previousNode()。顧名思義置侍,在深度優(yōu)先的 DOM 子樹遍歷中映之,nextNode() 方法用于向前前進(jìn)一步,而 previousNode() 用于向后后退一步蜡坊。
??在剛剛創(chuàng)建的 NodeIterator 對象中杠输,有一個內(nèi)部指針指向根節(jié)點,因此第一次調(diào)用 nextNode() 會返回根節(jié)點秕衙。當(dāng)遍歷到 DOM 子樹的最后一個節(jié)點時蠢甲,nextNode() 返回 null。
??previousNode() 方法的工作機(jī)制類似据忘。當(dāng)遍歷到 DOM 子樹的最后一個節(jié)點峡钓,且 previousNode() 返回根節(jié)點之后,再次調(diào)用它就會返回 null若河。以下面的 HTML 片段為例能岩。

<div id="div1">
    <p><b>Hello</b> world!</p>
    <ul>
        <li>List item 1</li>
        <li>List item 2</li>
        <li>List item 3</li>
    </ul>
</div>

??假設(shè)我們想要遍歷<div>元素中的所有元素,那么可以使用下列代碼萧福。

var div = document.getElementById("div1");
var iterator = document.createNodeIterator(div, NodeFilter.SHOW_ELEMENT, null, false);
var node = iterator.nextNode();
while (node !== null) {
    console.log(node.tagName); // 輸出標(biāo)簽名
    node = iterator.nextNode();
} 

??在上述例子中拉鹃,第一次調(diào)用 nextNode() 返回<p>元素。因為在到達(dá) DOM 子樹末端時 nextNode() 返回 null鲫忍,所以這里使用了 while 語句在每次循環(huán)時檢查對 nextNode() 的調(diào)用是否返回了 null膏燕。執(zhí)行上面的代碼會顯示如下標(biāo)簽名:

DIV
P
B
UL
LI
LI
LI

??也許用不著顯示那么多信息,你只想返回遍歷中遇到的<li>元素悟民。很簡單坝辫,只要使用一個過濾器即可,如下面的例子所示射亏。

var div = document.getElementById("div1");
var filter = function(node){
    return node.tagName.toLowerCase() == "li" ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
};
var iterator = document.createNodeIterator(div, NodeFilter.SHOW_ELEMENT, filter, false);
var node = iterator.nextNode();
while (node !== null) {
    console.log(node.tagName); // 輸出標(biāo)簽名
    node = iterator.nextNode();
}

??在上述例子中近忙,迭代器只會返回<li>元素竭业。
??由于 nextNode() 和 previousNode() 方法都基于 NodeIterator 在 DOM 結(jié)構(gòu)中的內(nèi)部指針工作,所以 DOM 結(jié)構(gòu)的變化會反映在遍歷的結(jié)果中及舍。

??Firefox 3.5 之前的版本沒有實現(xiàn) createNodeIterator() 方法未辆,但卻支持下一節(jié)要討論的 createTreeWalker() 方法。

3.2锯玛、TreeWalker

??TreeWalker 是 NodeIterator 的一個更高級的版本咐柜。除了包括 nextNode() 和 previousNode() 在內(nèi)的相同的功能之外,這個類型還提供了下列用于在不同方向上遍歷 DOM 結(jié)構(gòu)的方法攘残。

  • parentNode():遍歷到當(dāng)前節(jié)點的父節(jié)點拙友;
  • firstChild():遍歷到當(dāng)前節(jié)點的第一個子節(jié)點;
  • lastChild():遍歷到當(dāng)前節(jié)點的最后一個子節(jié)點歼郭;
  • nextSibling():遍歷到當(dāng)前節(jié)點的下一個同輩節(jié)點遗契;
  • previousSibling():遍歷到當(dāng)前節(jié)點的上一個同輩節(jié)點

??創(chuàng)建 TreeWalker 對象要使用 document.createTreeWalker() 方法,這個方法接受的 4 個參數(shù)與 document.createNodeIterator() 方法相同:作為遍歷起點的根節(jié)點实撒、要顯示的節(jié)點類型姊途、過濾器和一個表示是否擴(kuò)展實體引用的布爾值。
??由于這兩個創(chuàng)建方法很相似知态,所以很容易用 TreeWalker 來代替 NodeIterator捷兰,如下面的例子所示。

var div = document.getElementById("div1");
var filter = function(node){
    return node.tagName.toLowerCase() == "li" ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
};
var walker= document.createTreeWalker(div, NodeFilter.SHOW_ELEMENT, filter, false);
var node = iterator.nextNode();
while (node !== null) {
    console.log(node.tagName); // 輸出標(biāo)簽名
    node = iterator.nextNode();
}

??在這里负敏,filter 可以返回的值有所不同贡茅。除了 NodeFilter.FILTER_ACCEPT 和 NodeFilter.FILTER_SKIP 之外,還可以使用 NodeFilter.FILTER_REJECT其做。
??在使用 NodeIterator 對象時顶考,NodeFilter.FILTER_SKIP 與 NodeFilter.FILTER_REJECT 的作用相同:跳過指定的節(jié)點。
??但在使用 TreeWalker 對象時妖泄,NodeFilter.FILTER_SKIP 會跳過相應(yīng)節(jié)點繼續(xù)前進(jìn)到子樹中的下一個節(jié)點驹沿,而 NodeFilter.FILTER_REJECT 則會跳過相應(yīng)節(jié)點及該節(jié)點的整個子樹。
??例如蹈胡,將前面例子中的 NodeFilter.FILTER_SKIP 修改成NodeFilter.FILTER_REJECT渊季,結(jié)果就是不會訪問任何節(jié)點。這是因為第一個返回的節(jié)點是 <div>罚渐,它的標(biāo)簽名不是 "li"却汉,于是就會返回NodeFilter.FILTER_REJECT,
??這意味著遍歷會跳過整個子樹荷并。在這個例子中合砂,<div>元素是遍歷的根節(jié)點,于是結(jié)果就會停止遍歷源织。
??當(dāng)然翩伪,TreeWalker 真正強(qiáng)大的地方在于能夠在 DOM 結(jié)構(gòu)中沿任何方向移動微猖。使用 TreeWalker 遍歷 DOM 樹,即使不定義過濾器幻工,也可以取得所有<li>元素励两,如下面的代碼所示黎茎。

var div = document.getElementById("div1");
var walker = document.createTreeWalker(div, NodeFilter.SHOW_ELEMENT, null, false);
walker.firstChild(); // 轉(zhuǎn)到<p>
walker.nextSibling(); // 轉(zhuǎn)到<ul>
var node = walker.firstChild(); // 轉(zhuǎn)到第一個<li>
while (node !== null) {
    console.log(node.tagName);
    node = walker.nextSibling();
}

??因為我們知道<li>元素在文檔結(jié)構(gòu)中的位置囊颅,所以可以直接定位到那里,即使用 firstChild() 轉(zhuǎn)到<p>元素傅瞻,使用 nextSibling() 轉(zhuǎn)到<ul>元素踢代,然后再使用 firstChild() 轉(zhuǎn)到第一個<li>元素。
??注意嗅骄,此處 TreeWalker 只返回元素(由傳入到 createTreeWalker() 的第二個參數(shù)決定)胳挎。因此,可以放心地使用 nextSibling() 訪問每一個<li>元素溺森,直至這個方法最后返回 null慕爬。
??TreeWalker 類型還有一個屬性,名叫 currentNode屏积,表示任何遍歷方法在上一次遍歷中返回的節(jié)點医窿。通過設(shè)置這個屬性也可以修改遍歷繼續(xù)進(jìn)行的起點,如下面的例子所示炊林。

var node = walker.nextNode();
console.log(node === walker.currentNode); // true
walker.currentNode = document.body; // 修改起點

??與 NodeIterator 相比姥卢,TreeWalker 類型在遍歷 DOM 時擁有更大的靈活性。由于 IE 中沒有對應(yīng)的類型和方法渣聚,所以使用遍歷的跨瀏覽器解決方案非常少見独榴。

4、 范圍

??為了讓開發(fā)人員更方便地控制頁面奕枝,“DOM2 級遍歷和范圍”模塊定義了“范圍”(range)接口棺榔。
??通過范圍可以選擇文檔中的一個區(qū)域,而不必考慮節(jié)點的界限(選擇在后臺完成隘道,對用戶是不可見的)症歇。
??在常規(guī)的 DOM 操作不能更有效地修改文檔時,使用范圍往往可以達(dá)到目的薄声。Firefox当船、Opera、Safari 和 Chrome 都支持 DOM 范圍默辨。IE 以專有方式實現(xiàn)了自己的范圍特性德频。

4.1、 DOM 中的范圍

??DOM2 級在 Document 類型中定義了 createRange() 方法缩幸。在兼容 DOM 的瀏覽器中壹置,這個方法屬于 document 對象竞思。
??createRange 使用 hasFeature() 或者直接檢測該方法,都可以確定瀏覽器是否支持范圍钞护。

var supportsRange = document.implementation.hasFeature("Range", "2.0");
var alsoSupportsRange = (typeof document.createRange == "function");

??如果瀏覽器支持范圍盖喷,那么就可以使用 createRange() 來創(chuàng)建 DOM 范圍,如下所示:

var range = document.createRange();

??與節(jié)點類似难咕,新創(chuàng)建的范圍也直接與創(chuàng)建它的文檔關(guān)聯(lián)在一起课梳,不能用于其他文檔。創(chuàng)建了范圍之后余佃,接下來就可以使用它在后臺選擇文檔中的特定部分暮刃。而創(chuàng)建范圍并設(shè)置了其位置之后,還可以針對范圍的內(nèi)容執(zhí)行很多種操作爆土,從而實現(xiàn)對底層 DOM 樹的更精細(xì)的控制椭懊。
??每個范圍由一個 Range 類型的實例表示,這個實例擁有很多屬性和方法步势。下列屬性提供了當(dāng)前范圍在文檔中的位置信息氧猬。

  • startContainer:包含范圍起點的節(jié)點(即選區(qū)中第一個節(jié)點的父節(jié)點)。
  • startOffset:范圍在 startContainer 中起點的偏移量坏瘩。如果 startContainer 是文本節(jié)點盅抚、注釋節(jié)點或 CDATA 節(jié)點,那么 startOffset 就是范圍起點之前跳過的字符數(shù)量桑腮。否則泉哈,startOffset 就是范圍中第一個子節(jié)點的索引。
  • endContainer:包含范圍終點的節(jié)點(即選區(qū)中最后一個節(jié)點的父節(jié)點)破讨。
  • endOffset:范圍在 endContainer 中終點的偏移量(與 startOffset 遵循相同的取值規(guī)則)丛晦。
  • commonAncestorContainer:startContainer 和 endContainer 共同的祖先節(jié)點在文檔樹中位置最深的那個。

??在把范圍放到文檔中特定的位置時提陶,這些屬性都會被賦值烫沙。

1. 用 DOM 范圍實現(xiàn)簡單選擇

??要使用范圍來選擇文檔中的一部分,最簡的方式就是使用 selectNode() 或 selectNodeContents()隙笆。這兩個方法都接受一個參數(shù)锌蓄,即一個 DOM 節(jié)點,然后使用該節(jié)點中的信息來填充范圍撑柔。
??其中瘸爽,selectNode() 方法選擇整個節(jié)點,包括其子節(jié)點铅忿;而 selectNodeContents() 方法則只選擇節(jié)點的子節(jié)點剪决。以下面的 HTML 代碼為例。

<!DOCTYPE html>
<html>
    <body>
        <p id="p1"><b>Hello</b> world!</p>
    </body>
</html>

??我們可以使用下列代碼來創(chuàng)建范圍:

var range1 = document.createRange(),
    range2 = document.createRange(),
    p1 = document.getElementById("p1");
range1.selectNode(p1);
range2.selectNodeContents(p1);

??這里創(chuàng)建的兩個范圍包含文檔中不同的部分:rang1 包含<p>元素及其所有子元素,而 rang2 包含<b>元素柑潦、文本節(jié)點 "Hello" 和文本節(jié)點 "world!"(如下圖所示)享言。

??在調(diào)用 selectNode() 時,startContainer渗鬼、endContainer 和 commonAncestorContainer 都等于傳入節(jié)點的父節(jié)點览露,也就是這個例子中的 document.body。
??而 startOffset 屬性等于給定節(jié)點在其父節(jié)點的 childNodes 集合中的索引(在這個例子中是 1——因為兼容 DOM 的瀏覽器將空格算作一個文本節(jié)點)譬胎,endOffset 等于 startOffset 加 1(因為只選擇了一個節(jié)點)差牛。
??在調(diào)用 selectNodeContents() 時,startContainer银择、endContainer 和 commonAncestorContainer 等于傳入的節(jié)點多糠,即這個例子中的<p>元素累舷。而 startOffset 屬性始終等于 0浩考,因為范圍從給定節(jié)點的第一個子節(jié)點開始。最后被盈,endOffset 等于子節(jié)點的數(shù)量(node.childNodes.length)析孽,在這個例子中是 2。

??此外只怎,為了更精細(xì)地控制將哪些節(jié)點包含在范圍中袜瞬,還可以使用下列方法。

  • setStartBefore(refNode):將范圍的起點設(shè)置在 refNode 之前身堡,因此 refNode 也就是范圍選區(qū)中的第一個子節(jié)點邓尤。同時會將 startContainer 屬性設(shè)置為 refNode.parentNode,將 startOffset 屬性設(shè)置為 refNode 在其父節(jié)點的 childNodes 集合中的索引贴谎。
  • setStartAfter(refNode):將范圍的起點設(shè)置在 refNode 之后汞扎,因此 refNode 也就不在范圍之內(nèi)了,其下一個同輩節(jié)點才是范圍選區(qū)中的第一個子節(jié)點擅这。同時會將 startContainer 屬性設(shè)置為 refNode.parentNode澈魄,將 startOffset 屬性設(shè)置為 refNode 在其父節(jié)點的 childNodes 集合中的索引加 1。
  • setEndBefore(refNode):將范圍的終點設(shè)置在 refNode 之前仲翎,因此 refNode 也就不在范圍之內(nèi)了痹扇,其上一個同輩節(jié)點才是范圍選區(qū)中的最后一個子節(jié)點。同時會將 endContainer 屬性設(shè)置為 refNode.parentNode溯香,將 endOffset 屬性設(shè)置為 refNode 在其父節(jié)點的 childNodes 集合中的索引鲫构。
  • setEndAfter(refNode):將范圍的終點設(shè)置在 refNode 之后,因此 refNode 也就是范圍選區(qū)中的最后一個子節(jié)點玫坛。同時會將 endContainer 屬性設(shè)置為 refNode.parentNode结笨,將 endOffset 屬性設(shè)置為 refNode 在其父節(jié)點的 childNodes 集合中的索引加 1。

??在調(diào)用這些方法時,所有屬性都會自動為你設(shè)置好禀梳。不過杜窄,要想創(chuàng)建復(fù)雜的范圍選區(qū),也可以直接指定這些屬性的值算途。

2. 用 DOM 范圍實現(xiàn)復(fù)雜選擇

??要創(chuàng)建復(fù)雜的范圍就得使用 setStart() 和 setEnd() 方法塞耕。這兩個方法都接受兩個參數(shù):一個參照節(jié)點和一個偏移量值。
??對 setStart() 來說嘴瓤,參照節(jié)點會變成 startContainer扫外,而偏移量值會變成 startOffset。
??對于 setEnd()來說廓脆,參照節(jié)點會變成 endContainer筛谚,而偏移量值會變成 endOffset。

??可以使用這兩個方法來模仿 selectNode() 和 selectNodeContents()停忿。來看下面的例子:

var range1 = document.createRange(),
    range2 = document.createRange(),
    p1 = document.getElementById("p1");
    p1Index = -1,
    i, len;
for (i=0, len=p1.parentNode.childNodes.length; i < len; i++) {
    if (p1.parentNode.childNodes[i] == p1) {
        p1Index = i;
        break;
    }
}

range1.setStart(p1.parentNode, p1Index);
range1.setEnd(p1.parentNode, p1Index + 1);
range2.setStart(p1, 0);
range2.setEnd(p1, p1.childNodes.length);

??顯然驾讲,要選擇這個節(jié)點(使用 range1),就必須確定當(dāng)前節(jié)點(p1)在其父節(jié)點的 childNodes 集合中的索引席赂。
??而要選擇這個節(jié)點的內(nèi)容(使用 range2)吮铭,也不必計算什么;只要通過 setStart() 和 setEnd()設(shè)置默認(rèn)值即可颅停。
??模仿 selectNode() 和 selectNodeContents() 并不是 setStart() 和 setEnd() 的主要用途谓晌,它們更勝一籌的地方在于能夠選擇節(jié)點的一部分。
??假設(shè)你只想選擇前面 HTML 示例代碼中從"Hello"的"llo"到"world!"的"o"——很容易做到癞揉。第一步是取得所有節(jié)點的引用纸肉,如下面的例子所示:

var p1 = document.getElementById("p1"),
    helloNode = p1.firstChild.firstChild,
    worldNode = p1.lastChild;

??實際上,"Hello"文本節(jié)點是<p>元素的孫子節(jié)點喊熟,因為它本身是<b>元素的一個子節(jié)點柏肪。因此,p1.firstChild 取得的是<b>逊移,而p1.firstChild.firstChild 取得的才是這個文本節(jié)點预吆。
??"world!"文本節(jié)點是<p>元素的第二個子節(jié)點(也是最后一個子節(jié)點),因此可以使用 p1.lastChild 取得該節(jié)點胳泉。
??然后拐叉,必須在創(chuàng)建范圍時指定相應(yīng)的起點和終點,如下面的例子所示扇商。

var range = document.createRange();
range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);

??因為這個范圍的選區(qū)應(yīng)該從"Hello"中"e"的后面開始凤瘦,所以在 setStart() 中傳入 helloNode 的同時,傳入了偏移量 2(即"e"的下一個位置案铺;"H"的位置是 0)蔬芥。
??設(shè)置選區(qū)的終點時,在 setEnd() 中傳入 worldNode 的同時傳入了偏移量 3,表示選區(qū)之外的第一個字符的位置笔诵,這個字符是"r"返吻,它的位置是 3(位置 0 上還有一個空格)。如下圖所示:

??由于 helloNode 和 worldNode 都是文本節(jié)點乎婿,因此它們分別變成了新建范圍的 startContainer 和 endContainer测僵。此時 startOffset 和 endOffset 分別用以確定兩個節(jié)點所包含的文本中的位置,而不是用以確定子節(jié)點的位置(就像傳入的參數(shù)為元素節(jié)點時那樣)谢翎。此時的 commonAncestorContainer 是<p>元素捍靠,也就是同時包含這兩個節(jié)點的第一個祖先元素。
??當(dāng)然森逮,僅僅是選擇了文檔中的某一部分用處并不大榨婆。但重要的是,選擇之后才可以對選區(qū)進(jìn)行操作褒侧。

3. 操作 DOM 范圍中的內(nèi)容

??在創(chuàng)建范圍時 良风,內(nèi)部會為這個范圍創(chuàng)建一個文檔片段,范圍所屬的全部節(jié)點都被添加到了這個文檔片段中璃搜。為了創(chuàng)建這個文檔片段拖吼,范圍內(nèi)容的格式必須正確有效。
??在前面的例子中这吻,我們創(chuàng)建的選區(qū)分別開始和結(jié)束于兩個文本節(jié)點的內(nèi)部,因此不能算是格式良好的 DOM 結(jié)構(gòu)篙议,也就無法通過 DOM 來表示唾糯。
??但是,范圍知道自身缺少哪些開標(biāo)簽和閉標(biāo)簽鬼贱,它能夠重新構(gòu)建有效的 DOM 結(jié)構(gòu)以便我們對其進(jìn)行操作移怯。
??對于前面的例子而言,范圍經(jīng)過計算知道選區(qū)中缺少一個開始的<b>標(biāo)簽这难,因此就會在后臺動態(tài)加入一個該標(biāo)簽舟误,同時還會在前面加入一個表示結(jié)束的</b>標(biāo)簽以結(jié)束"He"。于是姻乓,修改后的 DOM 就變成了如下所示嵌溢。

<p><b>He</b><b>llo</b> world!</p>

??另外,文本節(jié)點"world!"也被拆分為兩個文本節(jié)點蹋岩,一個包含"wo"赖草,另一個包含"rld!"。最終的 DOM 樹如下圖所示剪个,右側(cè)是表示范圍的文檔片段的內(nèi)容秧骑。

??像這樣創(chuàng)建了范圍之后,就可以使用各種方法對范圍的內(nèi)容進(jìn)行操作了(注意,表示范圍的內(nèi)部文檔片段中的所有節(jié)點乎折,都只是指向文檔中相應(yīng)節(jié)點的指針)绒疗。

??第一個方法,也是最容易理解的方法骂澄,就是 deleteContents()忌堂。這個方法能夠從文檔中刪除范圍所包含的內(nèi)容。例如:

var p1 = document.getElementById("p1"),
    helloNode = p1.firstChild.firstChild,
    worldNode = p1.lastChild,
    range = document.createRange(); 

range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);

range.deleteContents();

??執(zhí)行以上代碼后酗洒,頁面中會顯示如下 HTML 代碼:

<p><b>He</b>rld!</p>

??由于范圍選區(qū)在修改底層 DOM 結(jié)構(gòu)時能夠保證格式良好士修,因此即使內(nèi)容被刪除了,最終的 DOM結(jié)構(gòu)依舊是格式良好的樱衷。

??與 deleteContents() 方法相似棋嘲,extractContents() 也會從文檔中移除范圍選區(qū)。
??但這兩個方法的區(qū)別在于矩桂,extractContents() 會返回范圍的文檔片段沸移。利用這個返回的值,可以將范圍的內(nèi)容插入到文檔中的其他地方侄榴。如下面的例子所示:

var p1 = document.getElementById("p1"),
    helloNode = p1.firstChild.firstChild,
    worldNode = p1.lastChild,
    range = document.createRange();

range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);

var fragment = range.extractContents();
p1.parentNode.appendChild(fragment); 

??在這個例子中雹锣,我們將提取出來的文檔片段添加到了文檔<body>元素的末尾。(記住癞蚕,在將文檔片段傳入 appendChild() 方法中時蕊爵,添加到文檔中的只是片段的子節(jié)點,而非片段本身桦山。)結(jié)果得到如下 HTML 代碼:

<p><b>He</b>rld!</p>
<b>llo</b> wo

??還一種做法攒射,即使用 cloneContents() 創(chuàng)建范圍對象的一個副本,然后在文檔的其他地方插入該副本恒水。如下面的例子所示:

var p1 = document.getElementById("p1"),
    helloNode = p1.firstChild.firstChild,
    worldNode = p1.lastChild,
    range = document.createRange();

range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);

var fragment = range.cloneContents();
p1.parentNode.appendChild(fragment);

??這個方法與 extractContents() 非常類似会放,因為它們都返回文檔片段。它們的主要區(qū)別在于钉凌,cloneContents() 返回的文檔片段包含的是范圍中節(jié)點的副本咧最,而不是實際的節(jié)點。執(zhí)行上面的操作后御雕,頁面中的 HTML 代碼應(yīng)該如下所示:

<p><b>Hello</b> world!</p>
<b>llo</b> wo

??有一點請讀者注意矢沿,那就是在調(diào)用上面介紹的方法之前,拆分的節(jié)點并不會產(chǎn)生格式良好的文檔片段饮笛。換句話說咨察,原始的 HTML 在 DOM 被修改之前會始終保持不變。

4. 插入 DOM 范圍中的內(nèi)容

??利用范圍福青,可以刪除或復(fù)制內(nèi)容摄狱,還可以像前面介紹的那樣操作范圍中的內(nèi)容脓诡。使用 insertNode() 方法可以向范圍選區(qū)的開始處插入一個節(jié)點。假設(shè)我們想在前面例子中的 HTML 前面插入以下 HTML 代碼:

<span style="color: red">Inserted text</span>

??那么媒役,就可以使用下列代碼:

var p1 = document.getElementById("p1"),
    helloNode = p1.firstChild.firstChild,
    worldNode = p1.lastChild,
    range = document.createRange();

range.setStart(helloNode, 2);
range.setEnd(worldNode, 3);

var span = document.createElement("span");
span.style.color = "red";
span.appendChild(document.createTextNode("Inserted text"));

range.insertNode(span); 

??運行以上 JavaScript 代碼祝谚,就會得到如下 HTML 代碼:

<p id="p1"><b>He<span style="color: red">Inserted text</span>llo</b> world</p>

??注意,<span>正好被插入到了"Hello"中的"llo"前面酣衷,而該位置就是范圍選區(qū)的開始位置何荚。
??還要注意的是开呐,由于這里沒有使用上一節(jié)介紹的方法壳嚎,結(jié)果原始的 HTML 并沒有添加或刪除<b>元素框舔。
??使用這種技術(shù)可以插入一些幫助提示信息,例如在打開新窗口的鏈接旁邊插入一幅圖像啊片。

??除了向范圍內(nèi)部插入內(nèi)容之外只锻,還可以環(huán)繞范圍插入內(nèi)容,此時就要使用 surroundContents() 方法紫谷。這個方法接受一個參數(shù)齐饮,即環(huán)繞范圍內(nèi)容的節(jié)點。在環(huán)繞范圍插入內(nèi)容時笤昨,后臺會執(zhí)行下列步驟祖驱。

??(1) 提取出范圍中的內(nèi)容(類似執(zhí)行 extractContent());
??(2) 將給定節(jié)點插入到文檔中原來范圍所在的位置上瞒窒;
??(3) 將文檔片段的內(nèi)容添加到給定節(jié)點中捺僻。

??可以使用這種技術(shù)來突出顯示網(wǎng)頁中的某些詞句,例如下列代碼:

var p1 = document.getElementById("p1"),
    helloNode = p1.firstChild.firstChild,
    worldNode = p1.lastChild,
    range = document.createRange();

range.selectNode(helloNode);

var span = document.createElement("span");
span.style.backgroundColor = "yellow";

range.surroundContents(span);

??會給范圍選區(qū)加上一個黃色的背景根竿。得到的 HTML 代碼如下所示:

<p><b><span style="background-color:yellow">Hello</span></b> world!</p>

??為了插入<span>陵像,范圍必須包含整個 DOM 選區(qū)(不能僅僅包含選中的 DOM 節(jié)點)。

5. 折疊 DOM 范圍

??所謂折疊范圍寇壳,就是指范圍中未選擇文檔的任何部分∑拊酰可以用文本框來描述折疊范圍的過程壳炎。
??假設(shè)文本框中有一行文本,你用鼠標(biāo)選擇了其中一個完整的單詞逼侦。然后匿辩,你單擊鼠標(biāo)左鍵,選區(qū)消失榛丢,而光標(biāo)則落在了其中兩個字母之間铲球。
??同樣,在折疊范圍時晰赞,其位置會落在文檔中的兩個部分之間稼病,可能是范圍選區(qū)的開始位置选侨,也可能是結(jié)束位置。下圖展示了折疊范圍時發(fā)生的情形然走。

??使用 collapse() 方法來折疊范圍援制,這個方法接受一個參數(shù),一個布爾值芍瑞,表示要折疊到范圍的哪一端晨仑。參數(shù) true 表示折疊到范圍的起點,參數(shù) false 表示折疊到范圍的終點拆檬。
??要確定范圍已經(jīng)折疊完畢洪己,可以檢查 collapsed 屬性,如下所示:

range.collapse(true); // 折疊到起點
console.log(range.collapsed); // 輸出 true 

??檢測某個范圍是否處于折疊狀態(tài)竟贯,可以幫我們確定范圍中的兩個節(jié)點是否緊密相鄰答捕。例如,對于下面的 HTML 代碼:

<p id="p1">Paragraph 1</p><p id="p2">Paragraph 2</p>

??如果我們不知道其實際構(gòu)成(比如說澄耍,這行代碼是動態(tài)生成的)噪珊,那么可以像下面這樣創(chuàng)建一個范圍。

var p1 = document.getElementById("p1"),
    p2 = document.getElementById("p2"),
    range = document.createRange();

range.setStartAfter(p1);
range.setStartBefore(p2);

console.log(range.collapsed); // 輸出 true

??在上述例子中齐莲,新創(chuàng)建的范圍是折疊的痢站,因為 p1 的后面和 p2 的前面什么也沒有。

6. 比較 DOM 范圍

??在有多個范圍的情況下选酗,可以使用 compareBoundaryPoints() 方法來確定這些范圍是否有公共的邊界(起點或終點)阵难。這個方法接受兩個參數(shù):表示比較方式的常量值和要比較的范圍。

??compareBoundaryPoints 表示比較方式的常量值如下所示芒填。

  • Range.START_TO_START(0):比較第一個范圍和第二個范圍的起點呜叫;
  • Range.START_TO_END(1):比較第一個范圍的起點和第二個范圍的終點;
  • Range.END_TO_END(2):比較第一個范圍和第二個范圍的終點殿衰;
  • Range.END_TO_START(3):比較第一個范圍的終點和第一個范圍的起點朱庆。

??compareBoundaryPoints() 方法可能的返回值如下:如果第一個范圍中的點位于第二個范圍中的點之前,返回 -1闷祥;如果兩個點相等娱颊,返回 0;如果第一個范圍中的點位于第二個范圍中的點之后凯砍,返回 1箱硕。示例:

var range1 = document.createRange();
var range2 = document.createRange();
var p1 = document.getElementById("p1");

range1.selectNodeContents(p1);
range2.selectNodeContents(p1);
range2.setEndBefore(p1.lastChild);

console.log(range1.compareBoundaryPoints(Range.START_TO_START, range2)); // 0
console.log(range1.compareBoundaryPoints(Range.END_TO_END, range2)); // 1 

??在上述例子中,兩個范圍的起點實際上是相同的悟衩,因為它們的起點都是由 selectNodeContents() 方法設(shè)置的默認(rèn)值來指定的剧罩。因此,第一次比較返回 0座泳。但是惠昔,range2 的終點由于調(diào)用 setEndBefore() 已經(jīng)改變了幕与,結(jié)果是 range1 的終點位于 range2 的終點后面(如下圖所示),因此第二次比較返回 1舰罚。

7. 復(fù)制 DOM 范圍

??可以使用 cloneRange() 方法復(fù)制范圍纽门。這個方法會創(chuàng)建調(diào)用它的范圍的一個副本。

var newRange = range.cloneRange();

??新創(chuàng)建的范圍與原來的范圍包含相同的屬性营罢,而修改它的端點不會影響原來的范圍赏陵。

8. 清理 DOM 范圍

??在使用完范圍之后,最好是調(diào)用 detach() 方法饲漾,以便從創(chuàng)建范圍的文檔中分離出該范圍蝙搔。
??調(diào)用 detach()之后,就可以放心地解除對范圍的引用考传,從而讓垃圾回收機(jī)制回收其內(nèi)存了吃型。來看下面的例子。

range.detach(); // 從文檔中分離
range = null; // 解除引用

??在使用范圍的最后再執(zhí)行這兩個步驟是我們推薦的方式僚楞。一旦分離范圍勤晚,就不能再恢復(fù)使用了。

4.2泉褐、 IE8 及更早版本中的范圍

??雖然 IE9 支持 DOM 范圍赐写,但 IE8 及之前版本不支持 DOM 范圍。不過膜赃,IE8 及早期版本支持一種類似的概念挺邀,即文本范圍(text range)。
??文本范圍是 IE 專有的特性跳座,其他瀏覽器都不支持端铛。顧名思義,文本范圍處理的主要是文本(不一定是 DOM 節(jié)點)疲眷。通過<body>禾蚕、<button>、<input> 和 <textarea> 等這幾個元素狂丝,可以調(diào)用 createTextRange() 方法來創(chuàng)建文本范圍夕膀。以下是一個例子:

var range = document.body.createTextRange();

??像這樣通過 document 創(chuàng)建的范圍可以在頁面中的任何地方使用(通過其他元素創(chuàng)建的范圍則只能在相應(yīng)的元素中使用)。與 DOM 范圍類似美侦,使用 IE 文本范圍的方式也有很多種。

1. 用 IE 范圍實現(xiàn)簡單的選擇

??選擇頁面中某一區(qū)域的最簡單方式魂奥,就是使用范圍的 findText() 方法菠剩。這個方法會找到第一次出現(xiàn)的給定文本,并將范圍移過來以環(huán)繞該文本耻煤。如果沒有找到文本具壮,這個方法返回 false准颓;否則返回 true。同樣棺妓,仍然以下面的 HTML 代碼為例攘已。

<p id="p1"><b>Hello</b> world!</p>

??要選擇"Hello",可以使用下列代碼怜跑。

var range = document.body.createTextRange();
var found = range.findText("Hello"); 

??在執(zhí)行完第二行代碼之后样勃,文本"Hello"就被包圍在范圍之內(nèi)了。為此性芬,可以檢查范圍的 text 屬性來確認(rèn)(這個屬性返回范圍中包含的文本)峡眶,或者也可以檢查 findText() 的返回值——在找到了文本的情況下返回值為 true。例如:

console.log(found); // true
console.log(range.text); // "Hello"

??還可以為 findText() 傳入另一個參數(shù)植锉,即一個表示向哪個方向繼續(xù)搜索的數(shù)值辫樱。負(fù)值表示應(yīng)該從當(dāng)前位置向后搜索,而正值表示應(yīng)該從當(dāng)前位置向前搜索俊庇。因此狮暑,要查找文檔中前兩個"Hello"的實例,應(yīng)該使用下列代碼辉饱。

var found = range.findText("Hello");
var foundAgain = range.findText("Hello", 1);

??IE 中與 DOM 中的 selectNode() 方法最接近的方法是 moveToElementText()搬男,這個方法接受一個 DOM 元素,并選擇該元素的所有文本鞋囊,包括 HTML 標(biāo)簽止后。下面是一個例子。

var range = document.body.createTextRange();
var p1 = document.getElementById("p1");
range.moveToElementText(p1);

??在文本范圍中包含 HTML 的情況下溜腐,可以使用 htmlText 屬性取得范圍的全部內(nèi)容译株,包括 HTML 和文本,如下面的例子所示挺益。

console.log(range.htmlText);

??IE 的范圍沒有任何屬性可以隨著范圍選區(qū)的變化而動態(tài)更新歉糜。不過,其 parentElement() 方法倒是與 DOM 的 commonAncestorContainer 屬性類似望众。

var ancestor = range.parentElement();

??這樣得到的父元素始終都可以反映文本選區(qū)的父節(jié)點匪补。

2. 使用 IE 范圍實現(xiàn)復(fù)雜的選擇

??在 IE 中創(chuàng)建復(fù)雜范圍的方法,就是以特定的增量向四周移動范圍烂翰。為此夯缺,IE 提供了 4 個方法:move()、moveStart()甘耿、moveEnd() 和 expand()踊兜。這些方法都接受兩個參數(shù):移動單位和移動單位的數(shù)量。其中佳恬,移動單位是下列一種字符串值捏境。

  • "character":逐個字符地移動于游。
  • "word":逐個單詞(一系列非空格字符)地移動。
  • "sentence":逐個句子(一系列以句號垫言、問號或嘆號結(jié)尾的字符)地移動贰剥。
  • "textedit":移動到當(dāng)前范圍選區(qū)的開始或結(jié)束位置。

??通過 moveStart() 方法可以移動范圍的起點筷频,通過 moveEnd() 方法可以移動范圍的終點蚌成,移動的幅度由單位數(shù)量指定,如下面的例子所示截驮。

range.moveStart("word", 2); // 起點移動 2 個單詞
range.moveEnd("character", 1); // 終點移動 1 個字符

??使用 expand() 方法可以將范圍規(guī)范化笑陈。換句話說,expand() 方法的作用是將任何部分選擇的文本全部選中葵袭。
??例如涵妥,當(dāng)前選擇的是一個單詞中間的兩個字符,調(diào)用 expand("word") 可以將整個單詞都包含在范圍之內(nèi)坡锡。

??而 move() 方法則首先會折疊當(dāng)前范圍(讓起點和終點相等)蓬网,然后再將范圍移動指定的單位數(shù)量,如下面的例子所示鹉勒。

range.move("character", 5); // 移動 5 個字符

??調(diào)用 move() 之后帆锋,范圍的起點和終點相同,因此必須再使用 moveStart() 或 moveEnd() 創(chuàng)建新的選區(qū)禽额。

3. 操作 IE 范圍中的內(nèi)容

??在 IE 中操作范圍中的內(nèi)容可以使用 text 屬性或 pasteHTML() 方法锯厢。
??如前所述,通過 text 屬性可以取得范圍中的內(nèi)容文本脯倒;但是实辑,也可以通過這個屬性設(shè)置范圍中的內(nèi)容文本。示例:

var range = document.body.createTextRange();
range.findText("Hello");
range.text = "Howdy";

??如果仍以前面的 Hello World 代碼為例藻丢,執(zhí)行以上代碼后的 HTML 代碼如下剪撬。

<p id="p1"><b>Howdy</b> world!</p>

??注意,在設(shè)置 text 屬性的情況下悠反,HTML 標(biāo)簽保持不變残黑。

??要向范圍中插入 HTML 代碼,就得使用 pasteHTML() 方法斋否,如下面的例子所示梨水。

var range = document.body.createTextRange();
range.findText("Hello");
range.pasteHTML("<em>Howdy</em>");

??執(zhí)行這些代碼后,會得到如下 HTML茵臭。

<p id="p1"><b><em>Howdy</em></b> world!</p>

??不過冰木,在范圍中包含 HTML 代碼時,不應(yīng)該使用 pasteHTML(),因為這樣很容易導(dǎo)致不可預(yù)料的結(jié)果——很可能是格式不正確的 HTML踊沸。

4. 折疊 IE 范圍

??IE 為范圍提供的 collapse() 方法與相應(yīng)的 DOM 方法用法一樣:傳入 true 把范圍折疊到起點,傳入 false 把范圍折疊到終點社证。例如:

range.collapse(true); // 折疊到起點

??可惜的是逼龟,沒有對應(yīng)的 collapsed 屬性讓我們知道范圍是否已經(jīng)折疊完畢。為此追葡,必須使用 boundingWidth 屬性腺律,該屬性返回范圍的寬度(以像素為單位)。如果 boundingWidth 屬性等于 0宜肉,就說明范圍已經(jīng)折疊了:

var isCollapsed = (range.boundingWidth == 0);

??此外匀钧,還有 boundingHeight、boundingLeft 和 boundingTop 等屬性谬返,雖然它們都不像 boundingWidth 那么有用之斯,但也可以提供一些有關(guān)范圍位置的信息。

5. 比較 IE 范圍

??IE 中的 compareEndPoints() 方法與 DOM 范圍的 compareBoundaryPoints() 方法類似遣铝。這個方法接受兩個參數(shù):比較的類型和要比較的范圍佑刷。
??比較類型的取值范圍是下列幾個字符串值:"StartToStart"、 "StartToEnd"酿炸、 "EndToEnd" 和 "EndToStart"瘫絮。 這幾種比較類型與比較 DOM 范圍時使用的幾個值是相同的。
??同樣與 DOM 類似的是填硕,compareEndPoints() 方法也會按照相同的規(guī)則返回值麦萤,即如果第一個范圍的邊界位于第二個范圍的邊界前面,返回-1扁眯;如果二者邊界相同壮莹,返回 0;如果第一個范圍的邊界位于第二個范圍的邊界后面恋拍,返回 1垛孔。

??仍以前面的 Hello World 代碼為例,下列代碼將創(chuàng)建兩個范圍施敢,一個
選擇"Hello world!"(包括<b>標(biāo)簽)周荐,另一個選擇"Hello"。

var range1 = document.body.createTextRange();
var range2 = document.body.createTextRange();

range1.findText("Hello world!");
range2.findText("Hello");

console.log(range1.compareEndPoints("StartToStart", range2)); // 0
console.log(range1.compareEndPoints("EndToEnd", range2)); // 1

??由于這兩個范圍共享同一個起點僵娃,所以使用 compareEndPoints() 比較起點返回 0概作。而 range1 的終點在 range2 的終點后面,所以 compareEndPoints() 返回 1默怨。

??IE 中還有兩個方法讯榕,也是用于比較范圍的:isEqual() 用于確定兩個范圍是否相等,inRange() 用于確定一個范圍是否包含另一個范圍。下面是相應(yīng)的示例愚屁。

var range1 = document.body.createTextRange();
var range2 = document.body.createTextRange();

range1.findText("Hello World");
range2.findText("Hello");

console.log("range1.isEqual(range2): " + range1.isEqual(range2)); // false
console.log("range1.inRange(range2):" + range1.inRange(range2)); // true

??這個例子使用了與前面相同的范圍來示范這兩個方法济竹。由于這兩個范圍的終點不同,所以它們不相等霎槐,調(diào)用 isEqual() 返回 false送浊。由于 range2 實際位于 range1 內(nèi)部,它的終點位于后者的終點之前丘跌、起點之后袭景,所以 range2 被包含在 range1 內(nèi)部,調(diào)用 inRange() 返回 true闭树。

6. 復(fù)制 IE 范圍

??在 IE 中使用 duplicate() 方法可以復(fù)制文本范圍耸棒,結(jié)果會創(chuàng)建原范圍的一個副本,如下面的例子所示报辱。

var newRange = range.duplicate();

??新創(chuàng)建的范圍會帶有與原范圍完全相同的屬性与殃。

小結(jié)

??DOM2 級規(guī)范定義了一些模塊,用于增強(qiáng) DOM1 級捏肢∧巫眩“DOM2 級核心”為不同的 DOM 類型引入了一些與 XML 命名空間有關(guān)的方法。這些變化只在使用 XML 或 XHTML 文檔時才有用鸵赫;對于 HTML 文檔沒有實際意義衣屏。

??除了與 XML 命名空間有關(guān)的方法外,“DOM2 級核心”還定義了以編程方式創(chuàng)建 Document 實例的方法辩棒,也支持了創(chuàng)建 DocumentType 對象狼忱。

??“DOM2 級樣式”模塊主要針對操作元素的樣式信息而開發(fā),其特性簡要總結(jié)如下一睁。

  • 每個元素都有一個關(guān)聯(lián)的 style 對象钻弄,可以用來確定和修改行內(nèi)的樣式。
  • 要確定某個元素的計算樣式(包括應(yīng)用給它的所有 CSS 規(guī)則)者吁,可以使用 getComputedStyle() 方法窘俺。
  • IE不支持 getComputedStyle() 方法,但為所有元素都提供了能夠返回相同信息 currentStyle 屬性复凳。
  • 可以通過 document.styleSheets 集合訪問樣式表瘤泪。
  • 除 IE 之外的所有瀏覽器都支持針對樣式表的這個接口,IE 也為幾乎所有相應(yīng)的 DOM 功能提供了自己的一套屬性和方法育八。

??“DOM2 級遍歷和范圍”模塊提供了與 DOM 結(jié)構(gòu)交互的不同方式对途,簡要總結(jié)如下。

  • 遍歷即使用 NodeIterator 或 TreeWalker 對 DOM 執(zhí)行深度優(yōu)先的遍歷髓棋。
  • NodeIterator 是一個簡單的接口实檀,只允許以一個節(jié)點的步幅前后移動惶洲。而 TreeWalker 在提供相同功能的同時,還支持在 DOM 結(jié)構(gòu)的各個方向上移動膳犹,包括父節(jié)點恬吕、同輩節(jié)點和子節(jié)點等方向。
  • 范圍是選擇 DOM 結(jié)構(gòu)中特定部分镣奋,然后再執(zhí)行相應(yīng)操作的一種手段币呵。
  • 使用范圍選區(qū)可以在刪除文檔中某些部分的同時,保持文檔結(jié)構(gòu)的格式良好侨颈,或者復(fù)制文檔中的相應(yīng)部分。
  • IE8 及更早版本不支持“DOM2 級遍歷和范圍”模塊芯义,但它提供了一個專有的文本范圍對象哈垢,可以用來完成簡單的基于文本的范圍操作。IE9 完全支持 DOM 遍歷扛拨。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末耘分,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子绑警,更是在濱河造成了極大的恐慌求泰,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,013評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件计盒,死亡現(xiàn)場離奇詭異渴频,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)北启,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評論 2 382
  • 文/潘曉璐 我一進(jìn)店門卜朗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人咕村,你說我怎么就攤上這事场钉。” “怎么了懈涛?”我有些...
    開封第一講書人閱讀 152,370評論 0 342
  • 文/不壞的土叔 我叫張陵逛万,是天一觀的道長。 經(jīng)常有香客問我批钠,道長宇植,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,168評論 1 278
  • 正文 為了忘掉前任价匠,我火速辦了婚禮当纱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘踩窖。我一直安慰自己坡氯,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,153評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著箫柳,像睡著了一般手形。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上悯恍,一...
    開封第一講書人閱讀 48,954評論 1 283
  • 那天库糠,我揣著相機(jī)與錄音,去河邊找鬼涮毫。 笑死瞬欧,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的罢防。 我是一名探鬼主播艘虎,決...
    沈念sama閱讀 38,271評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼咒吐!你這毒婦竟也來了野建?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,916評論 0 259
  • 序言:老撾萬榮一對情侶失蹤恬叹,失蹤者是張志新(化名)和其女友劉穎候生,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绽昼,經(jīng)...
    沈念sama閱讀 43,382評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡唯鸭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,877評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了绪励。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肿孵。...
    茶點故事閱讀 37,989評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖疏魏,靈堂內(nèi)的尸體忽然破棺而出停做,到底是詐尸還是另有隱情,我是刑警寧澤大莫,帶...
    沈念sama閱讀 33,624評論 4 322
  • 正文 年R本政府宣布蛉腌,位于F島的核電站,受9級特大地震影響只厘,放射性物質(zhì)發(fā)生泄漏烙丛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,209評論 3 307
  • 文/蒙蒙 一羔味、第九天 我趴在偏房一處隱蔽的房頂上張望河咽。 院中可真熱鬧,春花似錦赋元、人聲如沸忘蟹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽媚值。三九已至狠毯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間褥芒,已是汗流浹背嚼松。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留锰扶,地道東北人献酗。 一個月前我還...
    沈念sama閱讀 45,401評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像坷牛,于是被迫代替她去往敵國和親凌摄。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,700評論 2 345

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