JavaScript - W3C DOM簡介

HTML DOM

DOM Core(核心)提供HTML與XML文件瀏覽、處理和維護階層架構的功能。主要有二大功能:

  • 瀏覽(Navigator):走訪各節點
  • 參考(Reference):存取節點

提供一致的走訪方式,只要建立好樹狀結構,所有能走訪到的節點就是標籤物件,文字節點都可以使用相同的nodeValue屬性更改其內容。

DOM HTML

HTML專屬的DOM API,提供現成的物件模型。需使用id/name屬性或forms[]、images[]等物件集合才能取得標籤物件, 而且每種標籤物件都擁有不同的屬性。

DOM Core可使用在HTML與XML,DOM HTML則針對HTML。

每一節點都是一個物件

  • DOM HTML提供各節點物件的屬性和方法
  • DOM Core提供屬性來走訪節點

W3C DOM只能更改文字節點的內容,其它標籤物件的屬性和方法還是來自DOM HTML。

轉換為節點樹

<table>
  <tbody>
    <tr><td>HTML</td><td>CSS</td></tr>
    <tr><td>Javascript</td><td>jQuery</td></tr>
  </tbody>
</table>

瀏覽器會將由伺服器所傳遞過來的HTML轉換為一節點樹,節點樹一定由html元素為根節點,html底下一定有兩個子點節,head元素子節點與body元素子節點。我們可以把這一層一層的關係畫成圖,此圖又稱節點(樹)圖。

節點(樹)圖

以上面table結構為例,

節點(樹)
圖一:節點(樹)

節點(樹狀)結構:


  • 下一層的子節點 → Child Node → tbody是table的Child Node
  • 上一層的父節點 → Parent Node → table是tbody的Parent Node
  • 左右節點(兄弟) → Sibling Node → tr與tr或tr之下td與td,各為Sibling Node
  • 最下層葉節點 → Leaf Node → td為Leaf Node
  • 文字節點(HTML的內容) → Text Node → HTML/CSS/Javascript/jQuery

在轉換一般table時需注意,要先將table轉換成HTML 4.0版的表格,即擁有<tbody>標籤,這樣在訪問時,才不會出錯。

另注意<font>,IE在轉換時,會在<font>後多加一個文字節點#text,目前HTML標準已不建議使用<font>標籤,請改用CSS。

在DOM中有3種節點

  1. 元素節點:element node,各種標籤便是這些元素節點的名稱,元素節點可以包含其他元素,唯一沒有被包含只有根元素<html>
  2. 屬性節點:attribute node,屬性節點總是被包含在元素節點之中,例<a href="http://blog.kkbruce.net">KKBruce Blog</a>,a是元素節點名稱,href是屬性節點名稱
  3. 文字節點:text node,標籤裡具體的文字內容,網頁最終目的是向使用者展示內容

W3C DOM屬性

JavaScript W3C DOM唯讀屬性
名稱說明類型/傳回類型
firstChild傳回第一個子節點childNodes物件集合,包含此節點下所有的子節點Node
lastChild傳回最後一個子節點的childNodes物件集合,包含此節點下所有的子節點Node
parentNode傳回父節點的物件,如已是根節點,傳回nullNode
nextSibling傳回下一個兄弟節點物件,如已是最後一個節點,傳回nullNode
previousSibling傳回上一個兄弟節點物件,如已是第一個節點,傳回nullNode
nodeName傳回節點的HTML標籤(英文大寫)名稱String
nodeType傳回節點種類,1是標籤(element node)、2是屬性(attribute node)、3為文字(text node)Number
specified布林值,傳回在HTML標籤是否有設定指定的屬性值Boolean

W3C DOM讀寫屬性

JavaScript W3C DOM讀寫屬性
名稱說明類型/傳回類型
nodeValue存取文字節點(text node),其他種類的節點傳回nullString

W3C DOM物件集合

W3C DOM物件集合可以取得下一層子節點和節點的屬性。

JavaScript W3C DOM物件集合
名稱說明類型/傳回類型
attributes節點屬性的物件集合,可以直接使用名稱來存取,僅用於元素節點NameNodeMap
childNodes所有子節點的物件集合(Array),方法item(i)可訪問第i+1個節點NodeList

在IE中可以直接使用id/name屬性來取得指定的節點物件,但用此方法實作的程式碼相容性不佳, 建議先使用getElementById("id")來取得指定的節點物件,然後再實作以確保Browser相容性。例如,

// 相容性不好
myid.childNodes[0].firstChild.nodeValue = "ASP.NET MVC";

// 相容性好
var objValue = document.getElementById("myid");
objValue.childNodes[0].firstChild.nodeValue = "ASP.NET MVC";

IE與其他Browser中childNodes走訪不同問題

我們在使用childNodes走訪節點時,IE會依順序走訪各元素節點,但其他瀏覽器不光是元素節點,連它們之間的空格也被當成子節點計算。例如,

<ul id="comicList">
 <li>航海王</li>
 <li id="Naruto">火影忍者</li>
 <li>海棉寶寶</li>
<ul>

可以使用以下JavaScript在IE與其他瀏覽器測試,

var eleUL=document.getElementById("comicList");
var listName="";
if (ele.hasChildNodes()) {
 var eleLI = eleUL.childNodes;
 for (var i=0,len=eleLI.length;i<len;i++){
  listName += eleLI[i].nodeName + "\n";
 }
 alert(listName);
}

在IE的測試結果:

  1. LI
  2. LI
  3. LI

在Firefox測試結果:

  1. #text
  2. LI
  3. #text
  4. LI
  5. #text
  6. LI

解決瀏覽器走訪不同問題

function nextSib(node){
  var tempLast = node.parentNode.lastChild; //取得node的最後一個節點
  if (node ==tempLast) { //是否為最後一個節點
    return null;
  }
  var tempObj = node.nextSibling; //非最後一個,可找下一個Node
  while ( tempObj.nodeType!=1 && tempObj.nextSibling!=null) //nodeType不是元素節點且不是最後一個,即找到元素節點為止
    tempObj = tempObj.nextSibling; //往下找下一個
  return (tempObj.nodeType==1) ? tempObj:null; //如果是元素節點,傳回節點本身,否則傳回null
}

function prevSib(node){
  var tempFirst = node.parentNode.firstChild; //取得node的第一個節點
  if (node ==tempFirst) { //是否為第一個節點
    return null;
  }
  var tempObj = node.previousSibling; //非第一個,可往上找上一個Node
  while ( tempObj.nodeType!=1 && tempObj.previousSibling!=null) //nodeType不是元素節點且不是第一個,即找到元素節點為止
    tempObj = tempObj.previousSibling; //往上找上一個
  return (tempObj.nodeType==1) ? tempObj:null; //如果是元素節點,傳回節點本身,否則傳回null
}

我們可以透過這兩個函式來走訪,而不用擔心相容性問題。

var eleLI = document.getElementById("Naruto");
var nextItem = nextSib(eleLI);
var preItem = prevSib(eleLI);
if (nextItem != null) //傳回不是null,是元素節點
...

W3C DOM方法

W3C DOM提供方法能建立、刪除、複製、交換、取代節點。

流程 -
名稱範例說明
appendChildobj(父).appendChild(obj(子))在obj(父)節點新增子節點obj(子),傳回新增的節點物件
cloneNodeobjNew = objDup.cloneNode(deep)複製objDup節點的新節點物件,deep為false僅複製此節點,true連子節點的整個節點樹都複製
createElementobjNew = document.createElement("tagName")建立一個HTML節點物件
createTextNodeobjNew = document.createTextNode(string)建立文字節點
hasChildNodesobjNode.hasChildNodes()檢查節點是否擁有子節點,有為true
insertBeforeobj(父).insertBefore(obj(子),objBrother)在obj(父)節點前插入一個子節點obj(子),位置在objBrother節點前
removeChildobj.parentNode.removeChild(targetNode)先找到要刪除的節點,透過parentNode的removeChild()方法來刪除目標節點
replaceChildobj.parentNode.replaceChild(newNode,oldNode)先找到要替換的節點,透過parentNode的replaceChild()方法來替換節點
getAttributeobj.getAttribute("attr")讀取obj的attr屬性值,例如,obj.getAttribute("title");
setAttributeobj.setAttribute("attr","value")設定obj的attr屬性的值為value,例obj.setAttribute("src","bruce.png");

insertAfter()函式

DOM只有提供insertBefore()在目標元素之前插入新元素,或是appendChild()在父元素的childNodes尾新增新元素, 如果需要在特用元素後插入新元素,需自行撰寫。

function insertAfter(newElement, targetElement){
  // 取得目標元素的上層節點
  var tParent = targetElement.parentNode;
  // 檢查是否有子元素
  if (tParent.lastChild == targetElement)
    // 附加至子節點後
    tParent.appendChild(newElement);
  else
    // 新增至節點之前
    tParent.insertBefore(newElement, targetElement.nextSibling);
}

新增HTML Tag步驟

  1. 建立節點
  2. 進行處理

建立節點

// 新增HTML節點
var objNew = document.createElement("P"); 
// 新增文字節點
var objText = document.createTextNode("新增說明文字");

進行處理

// 將objNew節點加入objNode節點最後面
objNode.appendChild(objNew);
// 將objText文字節點加入objNew節點之後
objNew.appendChild(objText);

例如,在一個空的<table>可以一層一層加上<tr>、<td>來自由動態產生你所想要的<table>。

改變節點文字的安全步驟

  1. 刪除所有子節點:removeChild()
  2. 根據新內容,建立新內容節點:createElement(),createTextNode()
  3. 將新內容節點,附加在目標節點之下:appendChild()

function replaceNodeText(id, newText){
  var node=document.getElementById(id);
  while (node.firstChild)
    node.removeChild(node.firstChild);
  node.appendChild(document.createTextNode(newText));
}

DOM與CSS樣式

DOM提供了透過節點取用樣式的途徑。

className屬性:提供對節點樣式類別的存取。

alert(ele.className);
ele.className = "highlight";

style屬性:提供對單一樣式的存取。

ele.style.visibility = "hidden";
ele.style.display = "none";

3 則留言:

  1. 表格裡的"範例",例如「obj.parentNode.replaceChild(newNode,oldNode)」,不會自己"斷行",造成會超出頁面的問題,這我在想想怎麼改進。
    也謝謝你的回應!

    回覆刪除
  2. 我已經修改表格"斷行"問題,在主流瀏覽器及 1024 * 768 Monitor 上都能正常斷句。

    回覆刪除
  3. 謝謝分享,在寫多檔上傳,要加上移除檔案,再找怎麼移除節點,就借用了prevSib方法,感謝。

    回覆刪除

感謝您的留言,如果我的文章你喜歡或對你有幫助,按個「讚」或「分享」它,我會很高興的。