隨筆-314  評(píng)論-209  文章-0  trackbacks-0
          文檔對(duì)象模型(Document Object Model,DOM)是用于操縱 XML 和 HTML 數(shù)據(jù)的最常用工具之一,然而它的潛力卻很少被充分挖掘出來(lái)。通過(guò)利用 DOM 的優(yōu)勢(shì),并使它更加易用,您將獲得一款應(yīng)用于 XML 應(yīng)用程序(包括動(dòng)態(tài) Web 應(yīng)用程序)的強(qiáng)大工具。

          本期文章介紹了一位客串的專(zhuān)欄作家,同時(shí)也是我的朋友和同事 Dethe Elza。Dethe 在利用 XML 進(jìn)行 Web 應(yīng)用程序開(kāi)發(fā)方面經(jīng)驗(yàn)豐富,在此,我要感謝他對(duì)我在介紹使用 DOM 和 ECMAScript 進(jìn)行 XML 編程這一方面的幫助。請(qǐng)密切關(guān)注本專(zhuān)欄,以了解 Dethe 的更多專(zhuān)欄文章。 —— David Mertz

          DOM 是處理 XML 和 HTML 的標(biāo)準(zhǔn) API 之一。由于它占用內(nèi)存大、速度慢,并且冗長(zhǎng),所以經(jīng)常受到人們的指責(zé)。盡管如此,對(duì)于很多應(yīng)用程序來(lái)說(shuō),它仍然是最佳選擇,而且比 XML 的另一個(gè)主要 API —— SAX 無(wú)疑要簡(jiǎn)單得多。DOM 正逐漸出現(xiàn)在一些工具中,比如 Web 瀏覽器、SVG 瀏覽器、OpenOffice,等等。

          DOM 很好,因?yàn)樗且环N標(biāo)準(zhǔn),并且被廣泛地實(shí)現(xiàn),同時(shí)也內(nèi)置到其他標(biāo)準(zhǔn)中。作為標(biāo)準(zhǔn),它對(duì)數(shù)據(jù)的處理與編程語(yǔ)言無(wú)關(guān)(這可能是優(yōu)點(diǎn),也可能是缺點(diǎn),但至少使我們處理數(shù)據(jù)的方式變得一致)。DOM 現(xiàn)在不僅內(nèi)置于 Web 瀏覽器,而且也成為許多基于 XML 的規(guī)范的一部分。既然它已經(jīng)成為您的工具的一部分,并且或許您偶爾還會(huì)使用它,我想現(xiàn)在應(yīng)該充分利用它給我們帶來(lái)的功能了。

          在使用 DOM 一段時(shí)間后,您會(huì)看到形成了一些模式 —— 您想要反復(fù)做的事情。快捷方式可以幫助您處理冗長(zhǎng)的 DOM,并創(chuàng)建自解釋的、優(yōu)雅的代碼。這里收集了一些我經(jīng)常使用的技巧和訣竅,還有一些 JavaScript 示例。

          insertAfter 和 prependChild

          第一個(gè)訣竅就是“沒(méi)有訣竅”。DOM 有兩種方法將孩子節(jié)點(diǎn)添加到容器節(jié)點(diǎn)(常常是一個(gè) Element,也可能是一個(gè) DocumentDocumentFragment):appendChild(node)insertBefore(node, referenceNode)。看起來(lái)似乎缺少了什么。假如我想在一個(gè)參考節(jié)點(diǎn)后面插入或者由前新增(prepend)一個(gè)子節(jié)點(diǎn)(使新節(jié)點(diǎn)位于列表中的第一位),我該怎么做呢?很多年以來(lái),我的解決方法是編寫(xiě)下列函數(shù):


          清單 1. 插入和由前新增的錯(cuò)誤方法
          												
          														function insertAfter(parent, node, referenceNode) {
              if(referenceNode.nextSibling) {
                  parent.insertBefore(node, referenceNode.nextSibling);
              } else {
                  parent.appendChild(node);
              }
          }
          function prependChild(parent, node) {
              if (parent.firstChild) {
                  parent.insertBefore(node, parent.firstChild);
              } else {
                  parent.appendChild(node);
              }
          }
          
          												
          										

          實(shí)際上,像清單 1 一樣,insertBefore() 函數(shù)已經(jīng)被定義為,在參考節(jié)點(diǎn)為空時(shí)返回到 appendChild()。因此,您可以不使用上面的方法,而使用 清單 2 中的方法,或者跳過(guò)它們僅使用內(nèi)置函數(shù):


          清單 2. 插入和由前新增的正確方法
          												
          														function insertAfter(parent, node, referenceNode) {
              parent.insertBefore(node, referenceNode.nextSibling);
          }
          function prependChild(parent, node) {
              parent.insertBefore(node, parent.firstChild);
          }
          
          												
          										

          如果您剛剛接觸 DOM 編程,有必要指出的是,雖然您可以使多個(gè)指針指向一個(gè)節(jié)點(diǎn),但該節(jié)點(diǎn)只能存在于 DOM 樹(shù)中的一個(gè)位置。因此,如果您想將它插入到樹(shù)中,沒(méi)必要先將它從樹(shù)中移除,因?yàn)樗鼤?huì)自動(dòng)被移除。當(dāng)重新將節(jié)點(diǎn)排序時(shí),這種機(jī)制很方便,僅需將節(jié)點(diǎn)插入到新位置即可。

          根據(jù)這種機(jī)制,若想交換兩個(gè)相鄰節(jié)點(diǎn)(稱(chēng)為 node1node2)的位置,可以使用下列方案之一:

          												
          														
          																
          																		node1.parentNode.insertBefore(node2, node1);
          																
          														
          node1.parentNode.insertBefore(node2, node1);

          												
          														
          																
          																		node1.parentNode.insertBefore(node1.nextSibling, node1);
          																
          														
          node1.parentNode.insertBefore(node1.nextSibling, node1);





          回頁(yè)首


          還可以使用 DOM 做什么?

          Web 頁(yè)面中大量應(yīng)用了 DOM。若訪問(wèn) bookmarklets 站點(diǎn)(參閱 參考資料),您會(huì)發(fā)現(xiàn)很多有創(chuàng)意的簡(jiǎn)短腳本,它們可以重新編排頁(yè)面,提取鏈接,隱藏圖片或 Flash 廣告,等等。

          但是,因?yàn)?Internet Explorer 沒(méi)有定義 Node 接口常量(可以用于識(shí)別節(jié)點(diǎn)類(lèi)型),所以您必須確保在遺漏接口常量時(shí),首先為 Web 在 DOM 腳本中定義接口常量。


          清單 3. 確保節(jié)點(diǎn)被定義
          												
          														if (!window['Node']) {
              window.Node = new Object();
              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;
          }
          
          												
          										

          清單 4 展示如何提取包含在節(jié)點(diǎn)中的所有文本節(jié)點(diǎn):


          清單 4. 內(nèi)部文本
          												
          														function innerText(node) {
              // is this a text or CDATA node?
              if (node.nodeType == 3 || node.nodeType == 4) {
                  return node.data;
              }
              var i;
              var returnValue = [];
              for (i = 0; i < node.childNodes.length; i++) {
                  returnValue.push(innerText(node.childNodes[i]));
              }
              return returnValue.join('');
          }
          
          												
          										





          回頁(yè)首


          快捷方式

          人們常常抱怨 DOM 太過(guò)冗長(zhǎng),并且簡(jiǎn)單的功能也需要編寫(xiě)大量代碼。例如,如果您想創(chuàng)建一個(gè)包含文本并響應(yīng)點(diǎn)擊按鈕的 <div> 元素,代碼可能類(lèi)似于:


          清單 5. 創(chuàng)建 <div> 的“漫長(zhǎng)之路”
          												
          														function handle_button() {
              var parent = document.getElementById('myContainer');
              var div = document.createElement('div');
              div.className = 'myDivCSSClass';
              div.id = 'myDivId';
              div.style.position = 'absolute';
              div.style.left = '300px';
              div.style.top = '200px';
              var text = "This is the first text of the rest of this code";
              var textNode = document.createTextNode(text);
              div.appendChild(textNode);
              parent.appendChild(div);
          }
          
          												
          										

          若頻繁按照這種方式創(chuàng)建節(jié)點(diǎn),鍵入所有這些代碼會(huì)使您很快疲憊不堪。必須有更好的解決方案 —— 確實(shí)有這樣的解決方案!下面這個(gè)實(shí)用工具可以幫助您創(chuàng)建元素、設(shè)置元素屬性和風(fēng)格,并添加文本子節(jié)點(diǎn)。除了 name 參數(shù),其他參數(shù)都是可選的。


          清單 6. 函數(shù) elem() 快捷方式
          												
          														function elem(name, attrs, style, text) {
              var e = document.createElement(name);
              if (attrs) {
                  for (key in attrs) {
                      if (key == 'class') {
                          e.className = attrs[key];
                      } else if (key == 'id') {
                          e.id = attrs[key];
                      } else {
                          e.setAttribute(key, attrs[key]);
                      }
                  }
              }
              if (style) {
                  for (key in style) {
                      e.style[key] = style[key];
                  }
              }
              if (text) {
                  e.appendChild(document.createTextNode(text));
              }
              return e;
          }
          
          												
          										

          使用該快捷方式,您能夠以更加簡(jiǎn)潔的方法創(chuàng)建 清單 5 中的 <div> 元素。注意,attrsstyle 參數(shù)是使用 JavaScript 文本對(duì)象而給出的。


          清單 7. 創(chuàng)建 <div> 的簡(jiǎn)便方法
          												
          														function handle_button() {
              var parent = document.getElementById('myContainer');
              parent.appendChild(elem('div',
                {class: 'myDivCSSClass', id: 'myDivId'}
                {position: 'absolute', left: '300px', top: '200px'},
                'This is the first text of the rest of this code'));
          }
          
          												
          										

          在您想要快速創(chuàng)建大量復(fù)雜的 DHTML 對(duì)象時(shí),這種實(shí)用工具可以節(jié)省您大量的時(shí)間。模式在這里就是指,如果您有一種需要頻繁創(chuàng)建的特定的 DOM 結(jié)構(gòu),則使用實(shí)用工具來(lái)創(chuàng)建它們。這不但減少了您編寫(xiě)的代碼量,而且也減少了重復(fù)的剪切、粘貼代碼(錯(cuò)誤的罪魁禍?zhǔn)祝⑶以陂喿x代碼時(shí)思路更加清晰。





          回頁(yè)首


          接下來(lái)是什么?

          DOM 通常很難告訴您,按照文檔的順序,下一個(gè)節(jié)點(diǎn)是什么。下面有一些實(shí)用工具,可以幫助您在節(jié)點(diǎn)間前后移動(dòng):


          清單 8. nextNode 和 prevNode
          												
          														// return next node in document order
          function nextNode(node) {
              if (!node) return null;
              if (node.firstChild){
                  return node.firstChild;
              } else {
                  return nextWide(node);
              }
          }
          // helper function for nextNode()
          function nextWide(node) {
              if (!node) return null;
              if (node.nextSibling) {
                  return node.nextSibling;
              } else {
                  return nextWide(node.parentNode);
              }
          }
          // return previous node in document order
          function prevNode(node) {
              if (!node) return null;
              if (node.previousSibling) {
                return previousDeep(node.previousSibling);
              }
              return node.parentNode;
          }
          // helper function for prevNode()
          function previousDeep(node) {
              if (!node) return null;
              while (node.childNodes.length) {
                  node = node.lastChild;
              }
              return node;
          }
          
          												
          										





          回頁(yè)首


          輕松使用 DOM

          有時(shí)候,您可能想要遍歷 DOM,在每個(gè)節(jié)點(diǎn)調(diào)用函數(shù)或從每個(gè)節(jié)點(diǎn)返回一個(gè)值。實(shí)際上,由于這些想法非常具有普遍性,所以 DOM Level 2 已經(jīng)包含了一個(gè)稱(chēng)為 DOM Traversal and Range 的擴(kuò)展(為迭代 DOM 所有節(jié)點(diǎn)定義了對(duì)象和 API),它用來(lái)為 DOM 中的所有節(jié)點(diǎn)應(yīng)用函數(shù)和在 DOM 中選擇一個(gè)范圍。因?yàn)檫@些函數(shù)沒(méi)有在 Internet Explorer 中定義(至少目前是這樣),所以您可以使用 nextNode() 來(lái)做一些類(lèi)似的事情。

          在這里,我們的想法是創(chuàng)建一些簡(jiǎn)單、普通的工具,然后以不同的方式組裝它們來(lái)達(dá)到預(yù)期的效果。如果您很熟悉函數(shù)式編程,這看起來(lái)會(huì)很親切。Beyond JS 庫(kù)(參閱 參考資料)將此理念發(fā)揚(yáng)光大。


          清單 9. 函數(shù)式 DOM 實(shí)用工具
          												
          														// return an Array of all nodes, starting at startNode and
          // continuing through the rest of the DOM tree
          function listNodes(startNode) {
              var list = new Array();
              var node = startNode;
              while(node) {
                  list.push(node);
                  node = nextNode(node);
              }
              return list;
          }
          // The same as listNodes(), but works backwards from startNode.
          // Note that this is not the same as running listNodes() and
          // reversing the list.
          function listNodesReversed(startNode) {
              var list = new Array();
              var node = startNode;
              while(node) {
                  list.push(node);
                  node = prevNode(node);
              }
              return list;
          }
          // apply func to each node in nodeList, return new list of results
          function map(list, func) {
              var result_list = new Array();
              for (var i = 0; i < list.length; i++) {
                  result_list.push(func(list[i]));
              }
              return result_list;
          }
          // apply test to each node, return a new list of nodes for which
          // test(node) returns true
          function filter(list, test) {
              var result_list = new Array();
              for (var i = 0; i < list.length; i++) {
                  if (test(list[i])) result_list.push(list[i]);
              }
              return result_list;
          }
          
          												
          										

          清單 9 包含了 4 個(gè)基本工具。listNodes()listNodesReversed() 函數(shù)可以擴(kuò)展到一個(gè)可選的長(zhǎng)度,這與 Arrayslice() 方法效果類(lèi)似,我把這個(gè)作為留給您的練習(xí)。另一個(gè)需要注意的是,map()filter() 函數(shù)是完全通用的,用于處理任何 列表(不只是節(jié)點(diǎn)列表)。現(xiàn)在,我向您展示它們的幾種組合方式。


          清單 10. 使用函數(shù)式實(shí)用工具
          												
          														// A list of all the element names in document order
          function isElement(node) {
              return node.nodeType == Node.ELEMENT_NODE;
          }
          function nodeName(node) {
              return node.nodeName;
          }
          var elementNames = map(filter(listNodes(document),isElement), nodeName);
          // All the text from the document (ignores CDATA)
          function isText(node) {
              return node.nodeType == Node.TEXT_NODE;
          }
          function nodeValue(node) {
              return node.nodeValue;
          }
          var allText = map(filter(listNodes(document), isText), nodeValue);
          
          												
          										

          您可以使用這些實(shí)用工具來(lái)提取 ID、修改樣式、找到某種節(jié)點(diǎn)并移除,等等。一旦 DOM Traversal and Range API 被廣泛實(shí)現(xiàn),您無(wú)需首先構(gòu)建列表,就可以用它們修改 DOM 樹(shù)。它們不但功能強(qiáng)大,并且工作方式也與我在上面所強(qiáng)調(diào)的方式類(lèi)似。

          DOM 的危險(xiǎn)地帶

          注意,核心 DOM API 并不能使您將 XML 數(shù)據(jù)解析到 DOM,或者將 DOM 序列化為 XML。這些功能都定義在 DOM Level 3 的擴(kuò)展部分“Load and Save”,但它們還沒(méi)有被完全實(shí)現(xiàn),因此現(xiàn)在不要考慮這些。每個(gè)平臺(tái)(瀏覽器或其他專(zhuān)業(yè) DOM 應(yīng)用程序)有自己在 DOM 和 XML 間轉(zhuǎn)換的方法,但跨平臺(tái)轉(zhuǎn)換不在本文討論范圍之內(nèi)。

          DOM 并不是十分安全的工具 —— 特別是使用 DOM API 創(chuàng)建不能作為 XML 序列化的樹(shù)時(shí)。絕對(duì)不要在同一個(gè)程序中混合使用 DOM1 非名稱(chēng)空間 API 和 DOM2 名稱(chēng)空間感知的 API(例如,createElementcreateElementNS)。如果您使用名稱(chēng)空間,請(qǐng)盡量在根元素位置聲明所有名稱(chēng)空間,并且不要覆蓋名稱(chēng)空間前綴,否則情況會(huì)非常混亂。一般來(lái)說(shuō),只要按照慣例,就不會(huì)觸發(fā)使您陷入麻煩的臨界情況。

          如果您一直使用 Internet Explorer 的 innerTextinnerHTML 進(jìn)行解析,那么您可以試試使用 elem() 函數(shù)。通過(guò)構(gòu)建類(lèi)似的一些實(shí)用工具,您會(huì)得到更多便利,并且繼承了跨平臺(tái)代碼的優(yōu)越性。將這兩種方法混合使用是非常糟糕的。

          某些 Unicode 字符并沒(méi)有包含在 XML 中。DOM 的實(shí)現(xiàn)使您可以添加它們,但后果是無(wú)法序列化。這些字符包括大多數(shù)的控制字符和 Unicode 代理對(duì)(surrogate pair)中的單個(gè)字符。只有您試圖在文檔中包含二進(jìn)制數(shù)據(jù)時(shí)才會(huì)遇到這種情況,但這是另一種轉(zhuǎn)向(gotcha)情況。





          回頁(yè)首


          結(jié)束語(yǔ)

          我已經(jīng)介紹了 DOM 能做的很多事情,但是 DOM(和 JavaScript)可以做的事情遠(yuǎn)不止這些。仔細(xì)研究、揣摩這些例子,看看是如何使用它們來(lái)解決可能需要客戶端腳本、模板或?qū)S?API 的問(wèn)題。

          DOM 有自己的局限性和缺點(diǎn),但同時(shí)也擁有眾多優(yōu)點(diǎn):它內(nèi)置于很多應(yīng)用程序中;無(wú)論使用 Java 技術(shù)、Python 或 JavaScript,它都以相同方式工作;它非常便于使用 SAX;使用上述的模板,它使用起來(lái)既簡(jiǎn)潔又強(qiáng)大。越來(lái)越多的應(yīng)用程序開(kāi)始支持 DOM,這包括基于 Mozilla 的應(yīng)用程序、OpenOffice 和 Blast Radius 的 XMetaL。越來(lái)越多的規(guī)范需要 DOM,并對(duì)它加以擴(kuò)展(例如,SVG),因此 DOM 時(shí)時(shí)刻刻就在您的身邊。使用這種被廣泛部署的工具,絕對(duì)是您的明智之舉。





          回頁(yè)首


          參考資料

          • 您可以參閱本文在 developerWorks 全球站點(diǎn)上的 英文原文

          • 下載 JavaScript 庫(kù),它包含了上面的腳本和一個(gè)用于測(cè)試這些腳本的簡(jiǎn)單 測(cè)試頁(yè)面

          • 直接訪問(wèn) DOM 發(fā)源地 —— W3C 的 DOM 資源頁(yè)面 ,其中包含到所有與文檔對(duì)象模型相關(guān)的標(biāo)準(zhǔn)的鏈接。

          • 查看 Jesse Ruderman 的 bookmarklets。雖然 Ruderman 沒(méi)有創(chuàng)造術(shù)語(yǔ)“bookmarkets”,但他收集了很多一流的、簡(jiǎn)短的、書(shū)簽似的 JavaScript,使用它們開(kāi)發(fā) DOM 的巨大潛力,使您的瀏覽器可以為您帶來(lái)更多幫助。

          • 訪問(wèn) Sjoerd Visscher 的 Beyond JS 庫(kù),它提供了遠(yuǎn)遠(yuǎn)超過(guò)我在這里提及的用于函數(shù)式編程的工具。如果您可以將事物抽象為函數(shù),那么 JavaScript 將會(huì)成為您得心應(yīng)手的工具。

          • DOM API 的標(biāo)準(zhǔn)參考在 W3C。這里是 DOM2 到 JavaScript (ECMAScript)映射 的網(wǎng)址。

          • 了解 AJAX 為什么已經(jīng)引起了這么大的反響。它使用了異步調(diào)用來(lái)使服務(wù)器實(shí)時(shí)升級(jí) Web 應(yīng)用程序。您可以使用上述的許多技術(shù),并閱讀 異步通信工具

          • 了解一下 XML 編輯器和工具的 XMetaL 系列,它們都支持 DOM API。它們由作者所在的公司 Blast Radius 開(kāi)發(fā)。

          • 在 developerWorks 的 Developer Bookstore 了解更多 XMl 相關(guān)的書(shū)籍,其中包括 David Mertz 的 Text Processing in Python一書(shū)。

          • 了解如何才能成為 IBM 認(rèn)證的 XML 及相關(guān)技術(shù)的開(kāi)發(fā)人員
          posted on 2006-08-22 15:21 xzc 閱讀(323) 評(píng)論(0)  編輯  收藏 所屬分類(lèi): XML
          主站蜘蛛池模板: 进贤县| 大冶市| 连南| 顺义区| 紫阳县| 阿鲁科尔沁旗| 高尔夫| 花垣县| 泗洪县| 宁南县| 新源县| 明水县| 孟津县| 千阳县| 兴隆县| 平顶山市| 印江| 互助| 霸州市| 三江| 乌苏市| 武功县| 枣庄市| 富阳市| 宣武区| 老河口市| 揭阳市| 大田县| 万全县| 吴忠市| 特克斯县| 和平县| 徐汇区| 邻水| 赣州市| 宝鸡市| 房产| 鄂托克前旗| 福贡县| 乌拉特中旗| 鹤峰县|