DOM 的全稱是文檔對(duì)象模型(Document Object Model)。它是 HTML 和 XML 文檔的 API。它定義了文檔的邏輯結(jié)構(gòu),以及對(duì)文檔進(jìn)行訪問和操作的方式。通過 DOM,開發(fā)人員可以在文檔中自由導(dǎo)航, 也可以添加、更新和刪除其中的元素和內(nèi)容?;旧衔臋n中的任何內(nèi)容都是可以通過 DOM 進(jìn)行訪問和操作的。本文詳細(xì)介紹了如何使用 DOM 基本 API 和 Dojo 來進(jìn)行 DOM 查詢和操作。使用的 Dojo 版本是 1.4。下面首先介紹 DOM 的基本概念。
DOM 是給腳本語(yǔ)言(如 JavaScript 和 VBScript 等)來使用的 API。在互聯(lián)網(wǎng)的早期,HTML 頁(yè)面都是靜態(tài)的。開發(fā)人員沒有辦法對(duì)頁(yè)面進(jìn)行動(dòng)態(tài)修改。DOM 的出現(xiàn)解決了這個(gè)問題。DOM 給出了一種描述 HTML 文檔結(jié)構(gòu)的方式,并且允許開發(fā)人員通過 DOM 提供的 API 來對(duì)文檔結(jié)構(gòu)進(jìn)行修改。DOM 目前是 W3C 的推薦規(guī)范。主流的瀏覽器都實(shí)現(xiàn)或部分實(shí)現(xiàn)該規(guī)范。下面首先介紹 DOM 規(guī)范的版本歷史。
DOM 從出現(xiàn)之后,經(jīng)過了不斷的發(fā)展變化,以及 W3C 組織的標(biāo)準(zhǔn)化工作,因此目前的版本比較多,具體如下所示:
getElementById()
方法、DOM 遍歷和范圍、名稱空間和 CSS 的支持。adoptNode()
和 textContent
等方法和屬性、文檔保存和加載、文檔驗(yàn)證和 XPath 等。本文中將重點(diǎn)介紹 DOM 級(jí)別 1 和級(jí)別 2 的部分。這些部分的內(nèi)容目前在不同瀏覽器之間的兼容性較好,而且也很常用。下面重點(diǎn)介紹 DOM 規(guī)范中的基本元素。
對(duì)于 HTML 文檔中的基本元素,DOM 都有一個(gè)抽象的接口與它對(duì)應(yīng)。
java.util.List
接口??梢酝ㄟ^節(jié)點(diǎn)在集合中的序號(hào)來獲取集合中的某個(gè)節(jié)點(diǎn)。java.util.Map
接口。
這里需要注意的是節(jié)點(diǎn)列表中的節(jié)點(diǎn)是動(dòng)態(tài)的,它反映的是最新的文檔結(jié)構(gòu)。比如通過 DOM API 獲得了某個(gè)元素的子節(jié)點(diǎn)列表,如果其中的某個(gè)子節(jié)點(diǎn)被刪除,此節(jié)點(diǎn)就不會(huì)出現(xiàn)在之前的節(jié)點(diǎn)列表中。
在介紹完 DOM 的基本概念之后,下面介紹如何使用 DOM 對(duì)當(dāng)前文檔樹進(jìn)行查詢。
通過 DOM 提供的 API 來對(duì)當(dāng)前文檔樹進(jìn)行查詢,是操作文檔的前提。由于文檔樹結(jié)構(gòu)可能很復(fù)雜,查詢到所需節(jié)點(diǎn)的操作有可能會(huì)比較繁瑣。這里介紹兩種方法來進(jìn)行查詢,一種是利用 DOM 規(guī)范中定義的基本 API,另外一種是使用 Dojo。下面先從基本 API 開始。
使用 DOM 規(guī)范中提供的 API,就可以對(duì)文檔進(jìn)行查詢,以及在文檔中自由導(dǎo)航。下面給出一些常用的方法和屬性。
首先介紹的是兩個(gè)用來在文檔樹中快速查找元素的方法:getElementById()
和 getElementsByTagName()
。
文檔接口的 getElementById(elementId)
方法是在 DOM 級(jí)別 2 中引入的。該方法的作用是在文檔中查找標(biāo)識(shí)符為 elementId
的元素。如果有,則返回該元素;否則返回 null
。對(duì) HTML 文檔來說,元素的標(biāo)識(shí)符是通過屬性 id
來指定的。如 document.getElementById("mySpan")
在當(dāng)前文檔中查找標(biāo)識(shí)符為 mySpan
的元素。
文檔和元素接口的 getElementsByTagName(tagname)
方法用來查找標(biāo)簽名為 tagname
的子元素。該方法的返回結(jié)果是節(jié)點(diǎn)列表,其中子元素的排列順序是樹遍歷時(shí)的先序順序。通過指定 tagname
的值為 *
,可以匹配所有標(biāo)簽。如 document.getElementsByTagName("div")
查找當(dāng)前文檔中所有的 div
元素。
下面介紹在查找到單個(gè)節(jié)點(diǎn)之后,如何查找其相鄰節(jié)點(diǎn)。
在文檔樹中,每個(gè)節(jié)點(diǎn)的具體類型不盡相同。在節(jié)點(diǎn)接口中定義了屬性 nodeType
用來獲取當(dāng)前節(jié)點(diǎn)的具體類型。該屬性的值是一系列預(yù)定義的常量值。屬性 nodeName
和 nodeValue
的值也與節(jié)點(diǎn)的具體類型相關(guān)。如對(duì)于元素節(jié)點(diǎn)來說,nodeName
的值是標(biāo)簽名稱,nodeValue
的值是 null
;對(duì)于屬性節(jié)點(diǎn)來說,nodeName
和 nodeValue
的值分別是屬性的名稱和值;對(duì)于文本節(jié)點(diǎn)來說,nodeName
的值是 #text
,nodeValue
的值是文本的內(nèi)容。
在訪問文檔樹的時(shí)候,一個(gè)常見的需求是訪問當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)、兄弟節(jié)點(diǎn)和子節(jié)點(diǎn)。節(jié)點(diǎn)接口中提供了相應(yīng)的屬性用來獲取這些節(jié)點(diǎn)。
parentNode
:獲取當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)。除了文檔、文檔片段和屬性之外的其它節(jié)點(diǎn)都可以擁有父節(jié)點(diǎn)。childNodes
:獲取當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn),是一個(gè)節(jié)點(diǎn)列表。hasChildNodes()
:該方法用來判斷當(dāng)前節(jié)點(diǎn)是否有子節(jié)點(diǎn)。firstChild
:獲取當(dāng)前節(jié)點(diǎn)的第一個(gè)子節(jié)點(diǎn)。如果沒有則返回 null
。lastChild
:獲取當(dāng)前節(jié)點(diǎn)的最后一個(gè)子節(jié)點(diǎn)。如果沒有則返回 null
。previousSibling
:獲取出現(xiàn)在當(dāng)前節(jié)點(diǎn)正前面的兄弟節(jié)點(diǎn)。如果沒有則返回 null
。nextSibling
:獲取出現(xiàn)在當(dāng)前節(jié)點(diǎn)正后面的兄弟節(jié)點(diǎn)。如果沒有則返回 null
。attributes
屬性用來獲取節(jié)點(diǎn)的屬性。對(duì)于元素節(jié)點(diǎn),返回的是一個(gè)命名節(jié)點(diǎn)映射表;對(duì)于其它類型的節(jié)點(diǎn),返回的是 null
。通過屬性 ownerDocument
可以獲取節(jié)點(diǎn)所在的文檔。
上面介紹的這些基本 API 是由瀏覽器來實(shí)現(xiàn)的。下面介紹 Dojo 提供的 dojo.query。
使用上面提到的 DOM 規(guī)范定義的基本 API,可以完成對(duì) HTML 文檔的查詢。不過基本 API 的主要問題在于所提供的方法粒度較細(xì)。即便是滿足一些簡(jiǎn)單的查詢需求,也需要相當(dāng)多的代碼量。比如查找某個(gè) div
元素下面所有的 span
元素,就需要用到 getElementById()
和 getElementsByTagName()
兩個(gè)方法。而對(duì) DOM 進(jìn)行查詢又是十分常用的操作,因此開發(fā)人員需要更加方便的進(jìn)行 DOM 查詢的方法。Dojo 中提供了 dojo.query 庫(kù),用來方便的進(jìn)行 DOM 查詢。dojo.query 的基本用法是使用 CSS 3 的選擇器語(yǔ)法來選擇 HTML 文檔中的節(jié)點(diǎn)。對(duì)于復(fù)雜的查詢條件,可以用復(fù)雜的 CSS 選擇器來描述。使用 dojo.query 可以極大的降低代碼量。比如上面提到的例子,用 dojo.query 的話只需要一行代碼就足夠了:dojo.query("#myDiv span")
。另外 dojo.query 使用的是 CSS 的選擇器語(yǔ)法,這對(duì)于開發(fā)人員來說并不陌生。代碼清單 1中給出了一些常用的 dojo.query 的用法。
dojo.query("#header > h1") //ID 為 header 的元素的直接子節(jié)點(diǎn)中的 h3 元素 dojo.query("span[title^='test']") // 屬性 title 以字符串 test 開頭的 span 元素 dojo.query("div[id$='widget']") // 屬性 id 以字符串 widget 結(jié)尾的 div 元素 dojo.query("input[name*='value']") // 屬性 name 包含子串 value 的 input 元素 dojo.query("#myDiv, .error") // 組合查詢,結(jié)果中包含 ID 為 myDiv 的元素和 CSS 類為 error 的元素 dojo.query(".message.info") // 同時(shí)包含了 CSS 類 message 和 info 的元素,注意兩個(gè)類之間不包含空格 dojo.query("tr:nth-child(even)") // 出現(xiàn)在父節(jié)點(diǎn)的偶數(shù)位置的 tr 元素 dojo.query("input[type=checkbox]:checked") // 所有選中狀態(tài)的復(fù)選框 dojo.query(".message:not(:nth-child(odd))") // 嵌套子查詢,選中包含 CSS 類 message, //并且不出現(xiàn)在父節(jié)點(diǎn)的奇數(shù)位置的元素 |
dojo.query
方法除了第一個(gè)必須的參數(shù)用來表示所用的選擇器語(yǔ)法之外,還有一個(gè)可選的參數(shù)用來指定查詢的范圍,可以是一個(gè) ID 或是元素。如果傳入該參數(shù),則查詢結(jié)果中只包含該元素的子節(jié)點(diǎn)。默認(rèn)的查詢范圍是整個(gè)文檔樹。如 dojo.query("span.info", "myDiv")
只在 ID 為 myDiv
的元素的子節(jié)點(diǎn)中查詢包含 CSS 類 info
的 span 元素。熟練使用 dojo.query 的前提條件是對(duì) CSS 3 規(guī)范定義的選擇器語(yǔ)法比較熟悉。關(guān)于 CSS 3 選擇器語(yǔ)法的更多信息,請(qǐng)見 參考資料。
dojo.query 的另外一個(gè)強(qiáng)大功能是可以對(duì)選擇出來的節(jié)點(diǎn)進(jìn)行統(tǒng)一處理。通過方法級(jí)聯(lián)還可以寫出非常簡(jiǎn)潔的代碼。下面的章節(jié)中將會(huì)詳細(xì)介紹 dojo.query 的這一能力。
在介紹完使用基本 API 和 dojo.query
進(jìn)行 DOM 查詢之后,下面介紹如何進(jìn)行 DOM 操作。
在通過上面介紹的基本 API 或是 dojo.query 查詢到所需的節(jié)點(diǎn)之后,下面就可以對(duì)這些節(jié)點(diǎn)進(jìn)行操作了。查詢是為操作服務(wù)的。對(duì) DOM 的操作包括對(duì)節(jié)點(diǎn)的創(chuàng)建、插入、更新和刪除操作。下面將具體介紹如何使用基本 API 和 Dojo 來完成 DOM 操作。
創(chuàng)建新的節(jié)點(diǎn)的統(tǒng)一入口是定義在文檔接口中的一系列方法。這些方法都以 create
開頭。常用的方法有 createElement(tagName)
用來創(chuàng)建一個(gè)標(biāo)簽名為 tagName
的元素;createTextNode(data)
用來創(chuàng)建一個(gè)內(nèi)容為 data
的文本節(jié)點(diǎn);createAttribute(name)
用來創(chuàng)建一個(gè)名稱為 name
的屬性節(jié)點(diǎn);createDocumentFragment()
用來創(chuàng)建一個(gè)文檔片段。
創(chuàng)建出新的節(jié)點(diǎn)之后,就需要將其插入到當(dāng)前文檔樹中。節(jié)點(diǎn)接口定義了兩個(gè)方法用來完成插入的操作。
appendChild(newChild)
:把節(jié)點(diǎn) newChild
添加到當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn)列表中。insertBefore(newChild, refChild)
:與 appendChild()
類似的是都是把節(jié)點(diǎn) newChild
添加到當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn)列表中,不同的是可以通過參數(shù) refChild
來指定位置。節(jié)點(diǎn) newChild
出現(xiàn)在節(jié)點(diǎn) refChild
的正前面。
節(jié)點(diǎn)接口的 replaceChild(newChild, oldChild)
方法用來將當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn) oldChild
替換成新的節(jié)點(diǎn) newChild
。方法 removeChild(oldChild)
用來刪除當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn) oldChild
。
對(duì)于元素節(jié)點(diǎn)來說,可以對(duì)其屬性進(jìn)行操作。方法 setAttribute(name, value)
用來設(shè)置名為 name
的屬性的值為 value
。方法 removeAttribute(name)
用來刪除名為 name
的屬性。
如果一個(gè)節(jié)點(diǎn)已經(jīng)在文檔樹中存在,通過上面提到的 appendChild()
、insertBefore()
和 replaceChild()
方法改變其在文檔樹中的位置的時(shí)候,該節(jié)點(diǎn)會(huì)首先被從文檔樹中刪除,然后再被插入到新的位置中。在插入文檔片段的時(shí)候,文檔片段本身并不會(huì)被插入,只有其子節(jié)點(diǎn)被插入到文檔樹中。
Dojo 也提供了一系列的 API 用來執(zhí)行 DOM 操作。下面介紹常用的方法。
dojo.place(node, refNode, position)
方法用來插入節(jié)點(diǎn)到文檔樹中的指定位置。該方法的參數(shù) node
用來指定待插入元素的 ID 或引用;refNode
用來指定插入元素時(shí)的參照元素;position
用來指定相對(duì)于參照元素的位置,可選的值有 before
、after
、replace
、only
、first
和 last
,分別表示在參照元素之前、之后、替換掉參照元素、替換掉參照元素的全部子節(jié)點(diǎn)、作為參照元素的第一個(gè)子元素,以及作為參照元素的最后一個(gè)子元素。也可以傳入表示在參照元素的子節(jié)點(diǎn)中的序號(hào)位置。last
是默認(rèn)值,其作用相當(dāng)于之前介紹的 appendChild()
方法。如果該方法的第一個(gè)參數(shù)是以“<
”開頭的字符串,則創(chuàng)建一個(gè)以該字符串為內(nèi)容的文檔片段并插入此片段。
Dojo 提供了 3 個(gè)與元素的屬性相關(guān)的方法。dojo.attr(node, name, value)
用來獲取或設(shè)置元素的屬性。該方法的參數(shù) node
用來指定元素的 ID 或是引用;name
用來指定要獲取或設(shè)置的屬性的名稱,也可以是一個(gè)包含“屬性 / 值”名值對(duì)的 JSON 對(duì)象;value
用來指定要設(shè)置的屬性的值。傳入兩個(gè)參數(shù)可以是獲取單個(gè)屬性的值,也可以是設(shè)置一組屬性的值。如 dojo.attr(node, "title")
用來獲取屬性 title
的值,dojo.attr(node, {"title" : "My Title", "tabIndex" : 1})
用來同時(shí)設(shè)置屬性 title
和 tabIndex
的值。傳入三個(gè)參數(shù)用來設(shè)置單個(gè)屬性的值,如 dojo.attr(node, "name", "username")
用來設(shè)置屬性 name
的值。在設(shè)置屬性的時(shí)候,可以傳入方法作為參數(shù)用來綁定事件處理。dojo.hasAttr(node, name)
用來判斷元素是否有名為 name
的屬性。dojo.removeAttr(node, name)
用來刪除元素的名為 name
的屬性。
dojo.create(tag, attrs, refNode, pos)
方法用來創(chuàng)建新元素,并且可以指定元素的屬性和在文檔樹中的位置。該方法可以有 4 個(gè)參數(shù),只有第一個(gè)表示標(biāo)簽名的參數(shù) tag
是必須的。第二個(gè)參數(shù) attrs
指定元素的屬性,實(shí)現(xiàn)時(shí)使用 dojo.attr()
方法。最后兩個(gè)參數(shù)指定新創(chuàng)建的元素在文檔樹中的位置,實(shí)現(xiàn)時(shí)使用 dojo.place()
方法。
前面在介紹 dojo.query 的時(shí)候提到可以對(duì)選擇出來的節(jié)點(diǎn)進(jìn)行處理,下面進(jìn)行具體介紹。dojo.query()
方法返回的結(jié)果是 dojo.NodeList
對(duì)象。dojo.NodeList
繼承自 JavaScript 中的數(shù)組類型,并添加了很多實(shí)用的方法,可以很方便的對(duì)選擇出來的節(jié)點(diǎn)集合進(jìn)行操作。其中的很多方法的返回結(jié)果也是 dojo.NodeList
對(duì)象。這樣多個(gè)方法的調(diào)用就可以級(jí)聯(lián)起來,使得代碼更加簡(jiǎn)單。在這一點(diǎn)上,dojo.query 的用法與 jQuery 比較類似。具體的級(jí)聯(lián)用法見 dojo.query 級(jí)聯(lián)一節(jié)。
dojo.NodeList
中包含了與數(shù)組元素處理、DOM 操作、CSS 樣式處理和事件綁定相關(guān)的很多方法,下面具體介紹其中的實(shí)用方法,如下所示。
forEach()
、map()
、filter()
、slice()
、splice()
、indexOf()
、lastIndexOf()
、every()
和 some()
:這些是對(duì)節(jié)點(diǎn)數(shù)組本身進(jìn)行操作的方法。dojo.NodeList
的這些方法與操作數(shù)組的對(duì)應(yīng)方法的含義相同,只是操作的對(duì)象被隱式指定為當(dāng)前的節(jié)點(diǎn)數(shù)組。attr()
和 removeAttr()
:這兩個(gè)是用來操作元素屬性的方法,可以為節(jié)點(diǎn)數(shù)組中每個(gè)元素設(shè)置屬性值或刪除屬性值。如 dojo.query("a").attr("target", "_blank")
查找頁(yè)面中所有的 a
元素,并把其屬性 target
的值設(shè)成 _blank
。style()
、addClass()
、removeClass()
和 toggleClass()
:這些方法用來設(shè)置節(jié)點(diǎn)數(shù)組中每個(gè)元素的樣式和 CSS 類。如 dojo.query("p").style("fontSize", "1.2em")
把頁(yè)面上所有的 p
元素的字體大小設(shè)成 1.2em
。append()
、prepend()
、after()
和 before()
:這四個(gè)方法為節(jié)點(diǎn)數(shù)組中的每個(gè)元素添加內(nèi)容,只是新添加內(nèi)容的位置不同,分別位于節(jié)點(diǎn)的最后一個(gè)子節(jié)點(diǎn)、第一個(gè)子節(jié)點(diǎn)、之后和之前。這四個(gè)方法的參數(shù)可以是 HTML 字符串、DOM 節(jié)點(diǎn)引用和 dojo.NodeList
對(duì)象。如 dojo.query("p").after("<span>Hello</span>")
在每個(gè) p 元素之后添加一個(gè)新的 span 元素。appendTo()
、prependTo()
、insertBefore()
和 insertAfter()
:這四個(gè)方法與上面四個(gè)方法是分別對(duì)應(yīng)的,不同的是其參數(shù)是一個(gè) dojo.query 查詢字符串,節(jié)點(diǎn)數(shù)組中的元素被添加到由該查詢指定的節(jié)點(diǎn)的對(duì)應(yīng)位置上??梢钥闯墒巧厦嫠膫€(gè)方法的逆操作。如 dojo.query("span.message").appendTo("#main")
把包含 CSS 類 message
的 span 元素添加為 ID 為 main
的元素的最后一個(gè)子節(jié)點(diǎn)。wrap()
、wrapAll()
和 wrapInner()
:這三個(gè)方法用來包裝節(jié)點(diǎn)數(shù)組中的元素。wrap()
和 wrapInner()
都是對(duì)節(jié)點(diǎn)中的每個(gè)元素添加包裝,不同的是前者包裝的是元素本身,而后者包裝的是元素的子節(jié)點(diǎn)。wrapAll()
是包裝的節(jié)點(diǎn)數(shù)組中的全部元素。代碼清單 2中給出了這三個(gè)方法的用法。children()
、parent()
、next()
和 prev()
:這四個(gè)方法用來查詢節(jié)點(diǎn)數(shù)組中元素的子節(jié)點(diǎn)、父節(jié)點(diǎn)、后面和前面的相鄰節(jié)點(diǎn)。這些方法都接受一個(gè)查詢條件作為參數(shù)來進(jìn)一步過濾結(jié)果。如 dojo.query("#myDiv").chidren(".message")
查詢 ID 為 myDiv
的元素的包含 CSS 類 message
的子節(jié)點(diǎn)。
// 原始的 HTML 文檔片段 <div id="myDiv"> <span class="item"> <span class="title">Item 1</span> </span> <span class="item"> <span class="title">Item 2</span> </span> </div> // 執(zhí)行 dojo.query(".item").wrap("<div class='item-container'></div>") 之后的結(jié)果 <div id="myDiv"> <div class="item-container"> <span class="item"> <span class="title">Item 1</span> </span> </div> <div class="item-container"> <span class="item"> <span class="title">Item 1</span> </span> </div> </div> // 執(zhí)行 dojo.query(".item").wrapAll("<div class='items'></div>") 之后的結(jié)果 <div id="myDiv"> <div class="items"> <span class="item"> <span class="title">Item 1</span> </span> <span class="item"> <span class="title">Item 1</span> </span> </div> </div> // 執(zhí)行 dojo.query(".item").wrapInner("<div class='item-inner'></div>") 之后的結(jié)果 <div id="myDiv"> <span class="item"> <div class="item-inner"> <span class="title">Item 1</span> </div> </span> <span class="item"> <div class="item-inner"> <span class="title">Item 1</span> </div> </span> </div> |
dojo.query
方法返回的是 dojo.NodeList
對(duì)象,而 dojo.NodeList
對(duì)象的絕大多數(shù)方法返回的也是 dojo.NodeList
對(duì)象。這樣的話,對(duì) dojo.NodeList
的多個(gè)方法可以級(jí)聯(lián)起來,使得寫出來的代碼更加簡(jiǎn)潔。在使用級(jí)聯(lián)的時(shí)候需要注意 dojo.NodeList
中包含的節(jié)點(diǎn)的變化,以免在錯(cuò)誤的節(jié)點(diǎn)上面進(jìn)行操作。使用 end()
方法可以取消上一次對(duì) dojo.NodeList
的操作所造成的節(jié)點(diǎn)數(shù)組的改變。代碼清單 3給出了 dojo.query 級(jí)聯(lián)的示例。
// 原始的 HTML 片段 <div> <div class="item"> <div>Item 1</div> <div>Item 2</div> </div> </div> //JavaScript 代碼 dojo.query(".item").children().addClass("subItem").end() .parent().addClass("itemContainer"); // 更新之后的 HTML 片段 <div class="itemContainer"> <div class="item"> <div class="subItem">Item 1</div> <div class="subItem">Item 2</div> </div> </div> |
如 代碼清單 3所示,dojo.query(".item")
的節(jié)點(diǎn)數(shù)組中包含的是包含 CSS 類 item
的元素,調(diào)用 children()
之后,節(jié)點(diǎn)數(shù)組變?yōu)樯鲜鲈氐淖釉?,即兩個(gè) div
元素;addClass()
對(duì)這兩個(gè) div
元素進(jìn)行操作;接下來的 end()
方法則把節(jié)點(diǎn)數(shù)組還原成 children()
被調(diào)用之前的狀態(tài);接下來的 parent()
選擇的是包含 CSS 類 item
的元素的父元素,addClass()
對(duì)此父元素進(jìn)行操作??梢钥吹?,通過 end()
方法的使用可以在一條語(yǔ)句中執(zhí)行非常復(fù)雜的操作。不過從代碼的可讀性來說,一條語(yǔ)句中最好不要包含多個(gè) end()
。
在介紹完使用 DOM 基本 API 和 Dojo 進(jìn)行 DOM 操作之后,下面介紹在 Ajax 應(yīng)用中使用 DOM 的相關(guān)內(nèi)容。
DOM 查詢和操作在 Ajax 應(yīng)用中是非?;镜摹Mㄟ^ DOM 操作,可以動(dòng)態(tài)的對(duì)頁(yè)面進(jìn)行局部修改。這種“局部刷新”的用戶體驗(yàn),也是 Ajax 應(yīng)用相對(duì)于傳統(tǒng) Web 應(yīng)用的重要優(yōu)勢(shì)之一。一般來說,對(duì)頁(yè)面的局部修改由用戶的操作來觸發(fā)。用戶通過鼠標(biāo)和鍵盤觸發(fā)相應(yīng)的瀏覽器事件,在事件的響應(yīng)方法中進(jìn)行 DOM 查詢和操作。另外一種可能的觸發(fā)條件是瀏覽器中的定時(shí)器機(jī)制。一些局部修改可以完全在瀏覽器端來實(shí)現(xiàn),而另外一些局部修改則需要服務(wù)器端的支持。一般來說,在 Ajax 應(yīng)用中使用 DOM 有下面三種實(shí)現(xiàn)模式。
這三種模式的區(qū)別在于兩點(diǎn):服務(wù)器端返回?cái)?shù)據(jù)還是展示,瀏覽器端使用 DOM 操作還是模板。對(duì)于第一點(diǎn),服務(wù)器端返回?cái)?shù)據(jù)的好處是傳輸量較小、和客戶端的耦合較松散以及較容易支持除瀏覽器之外的其它客戶端。返回?cái)?shù)據(jù)的格式常見的有 XML 和 JSON。不足之處在于在瀏覽器端有比較多的邏輯來生成 HTML 片段。對(duì)于第二點(diǎn),DOM 操作的好處是簡(jiǎn)單易用,使用起來比較直接。不足之處在于代碼編寫比較復(fù)雜和冗長(zhǎng)。而使用模板的話,所生成的 HTML 片段的結(jié)構(gòu)可以從模板中很直觀的看到,修改起來比較方便。但是也增加了額外的復(fù)雜度。代碼清單 4給出了服務(wù)器端返回?cái)?shù)據(jù),瀏覽器端使用 DOM 操作的示例。
dojo.xhrGet({ url : "/posts", load : function(data) { var container = dojo.byId("posts"); for (var i = 0, n = data.length; i < n; i++) { var post = data[i]; var postNode = dojo.create("div", { className : "post" }, container); dojo.create("div", { className : "title", innerHTML : post.title }, postNode); dojo.create("div", { className : "content", innerHTML : post.content }, postNode); } }, error : function() { dojo.html.set(dojo.byId("posts"), "獲取文章出錯(cuò)。"); } }); |
如 代碼清單 4所示,服務(wù)器端返回的是 JSON 格式的數(shù)據(jù),在瀏覽器端使用 dojo.create()
來執(zhí)行 DOM 操作。代碼清單 5給出了服務(wù)器端返回?cái)?shù)據(jù),在瀏覽器端使用模板技術(shù)進(jìn)行 DOM 操作的示例。
var template = "<div class="post"><div class="title">${title}</div>" + "<div class="content">${content}</div></div>"; dojo.xhrGet({ url : "/posts", load : function(data) { var container = dojo.byId("posts"); for (var i = 0, n = data.length; i < n; i++) { var node = dojo.create("div", { innerHTML : dojo.string.substitute(template, data[i]); }); container.appendChild(node.firstChild); } }, error : function() { dojo.html.set(dojo.byId("posts"), "獲取文章出錯(cuò)。"); } }); |
如 代碼清單 5所示,template
中包含的就是 HTML 模板,從服務(wù)器端獲得數(shù)據(jù)之后,通過 dojo.string.substitute()
把數(shù)據(jù)應(yīng)用在模板上,從而得到所需的 HTML 片段內(nèi)容。
在介紹與在 Ajax 應(yīng)用中使用 DOM 相關(guān)的內(nèi)容之后,下面介紹與 DOM 查詢和操作相關(guān)的一些高級(jí)話題。
下面討論幾個(gè)與 DOM 查詢和操作相關(guān)的高級(jí)話題。首先從 DOM 操作的性能開始。
在 Ajax 應(yīng)用中,性能是一個(gè)很重要的問題。由于 DOM 操作 Ajax 應(yīng)用中非常普遍,提升 DOM 操作的性能對(duì)于整體的性能有很大影響。下面介紹一些好的實(shí)踐。
display
的值是 none
)的操作,另外一種是不在當(dāng)前文檔樹中的元素。由于文檔片段不在當(dāng)前文檔樹中,對(duì)它的修改并不會(huì)造成頁(yè)面的重新排列。innerHTML
:這種做法是通過字符串拼接來構(gòu)造 HTML 文檔,再通過設(shè)置元素的 innerHTML
來修改其內(nèi)容。使用 innerHTML
比一般的 DOM 操作要快。cloneNode()
:當(dāng)需要?jiǎng)?chuàng)建多個(gè)結(jié)構(gòu)相同的元素時(shí),比較好的辦法是首先創(chuàng)建出一個(gè)元素作為模板,然后用 cloneNode()
方法復(fù)制出其它的元素。這樣比逐個(gè)創(chuàng)建每個(gè)元素速度要快。需要注意的是,通過 cloneNode()
復(fù)制出來的元素會(huì)丟失原來綁定在其上的事件處理方法,需要重新進(jìn)行事件綁定。
代碼清單 6中給出了使用文檔片段和 cloneNode()
來提高 DOM 操作性能的示例。
var df = document.createDocumentFragment(); for (var i = 0; i < 10; i++) { dojo.create("div", { innerHTML : "node " + i }, df); } var node = dojo.byId("myDiv"); for (var i = 0; i < 10; i++) { node.appendChild(df.cloneNode(true)); } |
由于 DOM 規(guī)范的版本較多,時(shí)間跨度長(zhǎng),不同瀏覽器對(duì) DOM 規(guī)范的支持程度也不盡相同。目前來說,主流瀏覽器對(duì) DOM 規(guī)范級(jí)別 1 的全部以及級(jí)別 2 的核心部分,都有著不錯(cuò)的支持。在 Ajax 應(yīng)用中,應(yīng)該盡可能的使用這部分 DOM API。使用 JavaScript 庫(kù)也能減少兼容性問題。關(guān)于 DOM 的瀏覽器兼容性問題的細(xì)節(jié),見 參考資料。
前面提到 dojo.NodeList
提供了很多方法用來對(duì)查詢到的節(jié)點(diǎn)數(shù)組進(jìn)行操作。開發(fā)人員可以通過擴(kuò)展 dojo.NodeList
的方式來提供更加豐富的功能。這種擴(kuò)展方式類似于 jQuery 中的插件機(jī)制。下面通過開發(fā)一個(gè)插件來進(jìn)行說明。該插件實(shí)現(xiàn)的功能是點(diǎn)擊標(biāo)題欄可以控制內(nèi)容的展開和收縮。代碼清單 7給出了示例插件的 HTML 和 JavaScript 代碼。
//HTML 代碼片段 <div> <div class="toggler">Header 1</div> <div>Body 1</div> </div> //JavaScript 代碼 dojo.NodeList.prototype.toggler = function(options) { var opts = dojo.mixin({}, dojo.NodeList.prototype.toggler.defaults, options); var collapsedOnLoad = opts.collapsedOnLoad; return this.forEach(function(node) { dojo.connect(node, "onclick", function() { var collapsed = dojo.attr(node, "collapsed") == "true"; dojo.query(node).next().style("display", collapsed ? "" : "none"); dojo.attr(node, "collapsed", (!collapsed).toString()); }); if (collapsedOnLoad) { dojo.attr(node, "collapsed", "true"); dojo.query(node).next().style("display", "none"); } }); }; dojo.NodeList.prototype.toggler.defaults = { collapsedOnLoad : true }; dojo.addOnLoad(function() { dojo.query(".toggler").toggler(); }); |
在 代碼清單 7中,首先為 dojo.NodeList
添加新的方法 toggler()
。該方法可以對(duì)節(jié)點(diǎn)數(shù)組中的每個(gè)節(jié)點(diǎn)添加行為,使得該節(jié)點(diǎn)可以控制其相鄰的下一個(gè)節(jié)點(diǎn)是否顯示。具體的做法是通過 dojo.connect
進(jìn)行事件的綁定,當(dāng)點(diǎn)擊該節(jié)點(diǎn)的時(shí)候,根據(jù)節(jié)點(diǎn)的自定義屬性 collapsed
的值來確定其下一個(gè)節(jié)點(diǎn)的 CSS 樣式 display
的值。在使用的時(shí)候,只需要通過 dojo.query()
查詢到所需的節(jié)點(diǎn),再調(diào)用此方法即可。
dojo.behavior
允許以聲明的方式為頁(yè)面上的特定元素添加行為。進(jìn)行聲明的時(shí)候,只需要說明元素所滿足的模式,以及針對(duì)這些元素所應(yīng)用的行為即可。聲明模式的時(shí)候使用的是與 dojo.query
相同的 CSS 3 選擇器語(yǔ)法。對(duì)于每種模式,可以聲明多種不同的行為。對(duì)于每種行為,需要聲明其觸發(fā)的條件,以及對(duì)應(yīng)的動(dòng)作。觸發(fā)的條件一般有兩種:一種是找到匹配模式的元素,用 found
來聲明;另外一種則是元素上的各種事件,如 onclick
、onmouseover
和 onmouseout
等。第一種是默認(rèn)的觸發(fā)條件。對(duì)應(yīng)的動(dòng)作一般有兩種:一種是調(diào)用 JavaScript 方法,另外一種是用 dojo.publish
來發(fā)布某種主題的通知。在使用 dojo.behavior
的時(shí)候,首先通過 dojo.behavior.add()
來添加聲明,再通過 dojo.behavior.apply()
來應(yīng)用這些聲明。這些聲明的應(yīng)用是增量式的,同樣的聲明對(duì)于同樣的節(jié)點(diǎn)不會(huì)重復(fù)應(yīng)用。新添加的節(jié)點(diǎn)會(huì)應(yīng)用當(dāng)前所有的行為聲明。在頁(yè)面加載完成之后,dojo.behavior.apply()
會(huì)被自動(dòng)調(diào)用。
下面通過一個(gè)具體的實(shí)例來進(jìn)行說明。頁(yè)面中文本的截?cái)嗍且粋€(gè)很常見的操作。當(dāng)元素的大小不足以全部顯示其文本的時(shí)候,文本的一部分會(huì)被截?cái)?。一種比較好的做法是在被截?cái)嗟奈谋竞竺婕由?...
來提醒用戶。代碼清單 8中給出了用 dojo.behavior
實(shí)現(xiàn)文本截?cái)嗟拇a。
//HTML 代碼 <span class="label" maxLength="5">This label is very long.</span> //JavaScript 代碼 dojo.behavior.add({ ".label[maxLength]" : { found : function(node) { var text = node.innerHTML, maxLength = parseInt(dojo.attr(node, "maxLength")), truncatedText = text.length > maxLength ? text.substring(0, maxLength) + "..." : text; dojo.attr(node, "title", text); node.innerHTML = truncatedText; } } }); |
代碼清單 8中的行為聲明的含義是如果遇到包含 CSS 類 label
和屬性 maxLength
的元素,需要檢查其包含文本的長(zhǎng)度是否超過屬性 maxLength
指定的長(zhǎng)度。如果超過的話則進(jìn)行截?cái)唷?/p>
DOM 查詢和操作在 Ajax 應(yīng)用開發(fā)中十分常用。簡(jiǎn)潔高效的操作 DOM,是開發(fā)一個(gè)良好 Ajax 應(yīng)用的基礎(chǔ)。本文首先介紹了 DOM 的基本概念,接著介紹了如何分別利用 DOM 規(guī)范定義的基本 API 和 Dojo 來進(jìn)行 DOM 查詢和操作。最后討論了 DOM 操作的性能、dojo.NodeList 插件和 dojo.behavior 等高級(jí)話題。通過這些內(nèi)容的介紹,可以對(duì) Ajax 應(yīng)用中 DOM 查詢和操作有更深入的了解。
本人所發(fā)表的內(nèi)容僅為個(gè)人觀點(diǎn),不代表 IBM 公司立場(chǎng)、戰(zhàn)略和觀點(diǎn)。
描述 | 名字 | 大小 | 下載方法 |
---|---|---|---|
本文用到的 HTML 和 JavaScript 代碼示例1 | sample.zip | 4KB | HTTP |
注意:
學(xué)習(xí)
討論
成富任職于 IBM 中國(guó)軟件開發(fā)中心,目前在 Lotus 部門從事 IBM Mashup Center 的開發(fā)工作。他畢業(yè)于北京大學(xué)信息科學(xué)技術(shù)學(xué)院,獲得計(jì)算機(jī)軟件與理論專業(yè)碩士學(xué)位。他的個(gè)人網(wǎng)站是 http://www.cheng-fu.com。
聯(lián)系客服