網頁物件 (DOM)

早期 Internet Explorer 與 Netscape Navagator 兩大瀏覽器最主要的競爭方式,就是搶先實現出更多可程式化標籤的功能,搶先的結果演變成各自用不同的寫法來實作,導致互不相容,於是 W3C 制定了 DOM (Document Object Model) 統一規範。

DOM 的原理,就是先將 HTML 這類標記文件的結構,用程式設計常見的「樹狀結構」來展現,並制定一套函式介面來存取樹裡面的每個枝節,來達成一致寫法的規範標準。

因此學習 DOM,首先要知道標記文件變成樹狀結構後長怎樣,然後熟練相關的函式來控制這結構當中你想要的節點,這樣就能用程式來操作它。

換句話說,藉由 DOM 讓 JavaScript 得以完全掌控住 HTML 網頁的每個組成,這樣就能透過程式設計的寫法來修改整張 HTML 網頁的內容,完全達成「動態網頁設計」的目的;網頁不再寫死的,而是可以通過程式語言靈活變動的。


節點

在 DOM 中,標籤(tag)、標籤夾起來的內容(text)、屬性(attribute)、<!-- --> 註解(comment)、& 之類的實體(entity),都是節點(node)。

DOM 提供 Node 介面做為共通的屬性和方法,然後視功能需要繼承出其它介面,例如 Element 介面來處理標籤和屬性之間的關係。

appendChild(節點)加入節點到這個節點。
attributes傳回所有屬性。
childNodes所有子節點。
cloneNode()複製並傳回這個節點。
currentScript取得執行程式的 SCRIPT 標籤節點。
firstChild傳回第一個子節點。
hasAttributes()檢查這個節點是否有屬性。
hasChildNodes()檢查這個節點是否有子節點。
insertBefore(新節點,子節點)將新節點插入在子節點前。
lastChild傳回最後一個子節點。
nextSibling傳回這個節點的下一個節點。
nodeName傳回節點名稱。
nodeType傳回節點類型。
nodeValue存取節點內容。
parentNode傳回這個節點的父節點
previousSibling傳回這個節點的上一個節點
removeChild(節點)刪除子節點。
replaceChild(新節點,子節點)用新節點取代子節點。

傳回多個節點時會取得 NodeList 物件,當陣列物件看待即可,透過索引值直接取得裡面的 Node,或使用 length 取得節點數量。

產生新節點:

document.createElement(標籤名稱)產生標籤節點
document.createText(文字內容)產生內容節點
document.createAttribute()產生屬性節點
document.createComment()產生註解節點
document.createDocumentFragment()產生空白節點樹

節點類型:

ELEMENT_NODE1
ATTRIBUTE_NODE2
TEXT_NODE3
CDATA_SECTION_NODE4
ENTITY_REFERENCE_NODE5
ENTITY_NODE6
PROCESSING_INSTRUCTION_NODE7
COMMENT_NODE8
DOCUMENT_NODE9
DOCUMENT_TYPE_NODE10
DOCUMENT_FRAGMENT_NODE11
NOTATION_NODE12

來看些範例,首先是插入節點,例如在 BODY 標籤的開頭與結尾,各加入 HR 標籤:


要注意的是,空白和換行也算一個節點!所以 insertBefore() 第二參數使用 childNodes[索引值] 時,必須連空行節點也數進去!

然後是節點樹,用它來增加元素,再把節點樹增加到網頁,避免頻繁更新整個頁面的 DOM 結構,降低運算開銷:


元素

HTML 的標籤 (Tag) 在 DOM 改稱為元素 (Element),底下是 Element 介面的屬性和方法:

children所有子元素。
getAttribute(屬性)傳回屬性值。
getAttributeNode(屬性)傳回屬性節點。
hasAttribute(屬性)檢查是否有某個屬性。
removeAttribute(屬性)以名稱刪除屬性。
removeAttributeNode(屬性節點)以節點刪除屬性。
setAttribute(屬性,值)新增或設定屬性。
setAttributeNode(屬性節點)新增屬性。
tagName傳回標籤名稱。

由於網頁是由標籤和屬性構成,因此元素的使用,大多不在產生新的元素,而是取得現成的元素節點,也就是剖析文件!底下是 DOM 提供的方法,可用於 XML:

document.getElementById('ID 屬性')
document.getElementsByClassName('CLASS 屬性')[索引值]
document.getElementsByTagName('標籤名稱')[索引值]
document.querySelector('CSS 選擇器')
document.querySelectorAll('CSS 選擇器')[索引值]

也可以用 BOM 直接取得 HTML 的節點,但這不是 DOM 標準,不適用於 XML:

document.bodyBODY 元素節點
document.currentScript取得執行程式的 SCRIPT 元素節點
document.documentElement根元素,也就是 HTML 元素節點
document.embeds[索引值]EMBED 元素節點
document.forms[索引值]FORM 元素節點
document.forms[索引值].elements[索引值]FORM 元素節點和裡面的控制項
document.headHEAD 元素節點
document.images[索引值]IMG 元素節點
document.links[索引值]A 與 AREA 元素節點
document.plugins[索引值]PLUGIN 元素節點
document.scripts[索引值]SCRIPT 元素節點

「依 ID 名稱取得節點」以及「直接取得節點」是最直覺的!建議應用程式都採取這種做法來存取元素,有助於保障開發時效。

至於其它做法,通常是為了「剖析資料」用的,例如 Ajax 的通訊、或者像是 docx 附檔名的 Microsoft Office Word XML Format 文件。由於不知道節點的元素名稱與排列架構,只好用巡迴的方式逐筆處理資料,但如果不是這種場合,大可跳過這些做法。

另外,getElementsByTagName() 是傳回節點樹本身,所以查詢速度快,但每增刪節點都會動到整棵節點樹,所以增刪速度慢。querySelectorAll() 則是另外複製一棵小節點樹,所以查詢速度慢,但增刪節點只動這小棵節點樹,所以增刪速度快。

currentScript 如果寫在另一個 SCRIPT 標籤的函式來調用,將無法傳回元素節點,所以只能在當前 SCRIPT 標籤使用。


內容、屬性

取得元素節點後,來看 JavaScript 能用它來做哪些事~


存取標籤所括住的文字內容

元素節點.textContent


以 HTML 格式存取標籤所括住的內容

元素節點.innerHTML


照抄 HTML 標籤的屬性來設定元素

元素節點.標籤屬性=設定值


設定 CSS 樣式

元素節點.style.樣式名稱=設定值


變更 CSS 類型

元素節點.className=樣式類型


設定事件

1) 元素節點.事件=函式;
2) 元素節點.addEventListener(事件,函式,true);


首先來看 textContent,它可以讓你存取元素的文字內容。底下範例在 BODY 裡面新增一個 DIV 元素,然後又在 DIV 裡面新增一個 B 元素,最後在 B 元素使用 textContent 輸入 Hello 做為文字內容:


接著來看 innerHTML,它可以直接在元素節點裡面輸入 HTML 語法,上面的範例可以因此簡化為……


然後來看存取 HTML 標籤的屬性,例如 IMG 標籤有個 SRC 屬性,那 JavaScript 建立一個 IMG 元素時,就可以這樣來設定,比 setAttribute() 省事多了:


再來看設定 CSS 樣式,下面的範例將先前程式的 b 用 CSS 設定為紅色,且字型設定為「微軟正黑體」:


上面程式相當於如下 CSS 描述:

b { color:red; font-family:微軟正黑體 }

請注意 CSS 的 font-family 橫線,到了 JavaScript 改成 fontFamily 字母大寫。

但有些樣式名稱並不規則,例如 CSS 的 float 在 JavaScript 是 cssFolat。

最後來看事件,將 b 加入「在上面按滑鼠左鍵會跳出『別亂按』訊息」的功能:


addEventListener() 是 DOM 標準寫法,不像設定 on 事件會覆寫掉原先的功能,它是追加功能進去,按先後順序執行,並且可用 removeEventListener() 移除。它有第三個參數,設為 true 可以阻止往它層元素傳遞事件,也就是只在自己這層元素觸發事件。