DOM 簡介
文件物件模型 (DOM) 是構成網頁結構和內容的物件的資料表示。本指南將介紹 DOM,瞭解 DOM 如何在記憶體中表示 HTML 文件,以及如何使用 API 建立 Web 內容和應用程式。
什麼是 DOM?
文件物件模型 (DOM) 是 Web 文件的程式設計介面。它表示頁面,以便程式可以更改文件結構、樣式和內容。DOM 將文件表示為節點和物件;這樣,程式語言就可以與頁面互動。
網頁是可以顯示在瀏覽器視窗或作為 HTML 原始碼的文件。在這兩種情況下,都是同一個文件,但文件物件模型 (DOM) 表示允許對其進行操作。作為網頁的面向物件的表示,它可以使用 JavaScript 等指令碼語言進行修改。
例如,DOM 指定此程式碼片段中的 querySelectorAll 方法必須返回文件中所有 <p> 元素的列表
const paragraphs = document.querySelectorAll("p");
// paragraphs[0] is the first <p> element
// paragraphs[1] is the second <p> element, etc.
alert(paragraphs[0].nodeName);
所有用於操作和建立網頁的屬性、方法和事件都組織成物件。例如,表示文件本身的 document 物件,實現 HTMLTableElement DOM 介面以訪問 HTML 表格的任何 table 物件,等等,都是物件。
DOM 是使用多個協同工作的 API 構建的。核心 DOM 定義了描述任何文件及其內部物件的實體。根據需要,其他 API 透過向 DOM 新增新功能和能力來擴充套件它。例如,HTML DOM API 向核心 DOM 添加了對錶示 HTML 文件的支援,而 SVG API 添加了對錶示 SVG 文件的支援。
DOM 和 JavaScript
前面的簡短示例(就像幾乎所有示例一樣)是 JavaScript。也就是說,它是用 JavaScript 編寫的,但使用 DOM 訪問文件及其元素。DOM 不是程式語言,但如果沒有它,JavaScript 語言就不會有對網頁、HTML 文件、SVG 文件及其組成部分的模型或概念。整個文件、頭部、文件中的表格、表格標題、表格單元格中的文字以及文件中的所有其他元素都是該文件的文件物件模型的一部分。可以使用 DOM 和 JavaScript 等指令碼語言訪問和操作它們。
DOM 不是 JavaScript 語言的一部分,而是用於構建網站的 Web API。JavaScript 也可用於其他環境。例如,Node.js 在計算機上執行 JavaScript 程式,但提供了一組不同的 API,而 DOM API 不是 Node.js 執行時的核心部分。
DOM 的設計獨立於任何特定的程式語言,從而使文件的結構表示可以透過單個一致的 API 獲得。即使大多數 Web 開發人員只會透過 JavaScript 使用 DOM,也可以為任何語言構建 DOM 的實現,正如這個 Python 示例所示
# Python DOM example
import xml.dom.minidom as m
doc = m.parse(r"C:\Projects\Py\chap1.xml")
doc.nodeName # DOM property of document object
p_list = doc.getElementsByTagName("para")
有關在 Web 上編寫 JavaScript 所涉及的技術的更多資訊,請參閱 JavaScript 技術概述.
訪問 DOM
您無需執行任何特殊操作即可開始使用 DOM。您可以直接在稱為指令碼(瀏覽器執行的程式)的 JavaScript 中使用 API。
當您建立指令碼時,無論是將其內聯在 <script> 元素中還是將其包含在網頁中,您都可以立即開始使用 API 操作 document 或 window 物件,以操作文件本身或網頁中的任何元素(文件的子元素)。您的 DOM 程式設計可能像以下示例一樣簡單,該示例使用 console.log() 函式在控制檯中顯示一條訊息
<body onload="console.log('Welcome to my home page!');">
…
</body>
由於通常不建議將頁面的結構(用 HTML 編寫)與 DOM 的操作(用 JavaScript 編寫)混合在一起,因此 JavaScript 部分將在此處分組,並與 HTML 分開。
例如,以下函式建立一個新的 h1 元素,向該元素新增文字,然後將其新增到文件的樹中
<html lang="en">
<head>
<script>
// run this function when the document is loaded
window.onload = () => {
// create a couple of elements in an otherwise empty HTML page
const heading = document.createElement("h1");
const headingText = document.createTextNode("Big Head!");
heading.appendChild(headingText);
document.body.appendChild(heading);
};
</script>
</head>
<body></body>
</html>
基本資料型別
此頁面嘗試用簡單的術語描述各種物件和型別。但是,您應該瞭解 API 中傳遞的幾種不同的資料型別。
注意:由於絕大多數使用 DOM 的程式碼都圍繞著操作 HTML 文件,因此通常將 DOM 中的節點稱為元素,儘管嚴格來說,並非每個節點都是元素。
下表簡要描述了這些資料型別。
| 資料型別 (介面) | 描述 |
|---|---|
Document |
當成員返回型別為 document 的物件時(例如,元素的 ownerDocument 屬性返回其所屬的 document),此物件是根 document 物件本身。DOM document 參考 章節描述了 document 物件。 |
Node |
文件中的每個物件都是某種型別的節點。在 HTML 文件中,物件可以是元素節點,也可以是文字節點或屬性節點。 |
Element |
element 型別基於 node。它指的是由 DOM API 的成員返回的元素或型別為 element 的節點。與其說,例如,document.createElement() 方法返回對 node 的物件引用,不如說此方法返回剛剛在 DOM 中建立的 element。element 物件實現 DOM Element 介面,也實現更基本的 Node 介面,兩者都包含在此參考中。在 HTML 文件中,元素由 HTML DOM API 的 HTMLElement 介面以及描述特定型別元素能力的其他介面(例如,HTMLTableElement 用於 <table> 元素)進一步增強。 |
NodeList |
nodeList 是一個元素陣列,例如由 document.querySelectorAll() 方法返回的陣列。nodeList 中的專案可以透過以下兩種方式之一按索引訪問
item() 是 nodeList 物件上的唯一方法。後者使用典型的陣列語法來獲取列表中的第二個專案。 |
Attr |
當成員返回 attribute 時(例如,由 createAttribute() 方法返回),它是一個物件引用,它公開了一個用於屬性的特殊(儘管很小)介面。屬性是 DOM 中的節點,就像元素一樣,儘管您可能很少將它們用作節點。 |
NamedNodeMap |
namedNodeMap 類似於陣列,但專案可以透過名稱或索引訪問,儘管後者只是為了列舉的方便,因為它們在列表中沒有特定的順序。namedNodeMap 有一個 item() 方法用於此目的,您也可以向 namedNodeMap 新增和刪除專案。 |
還有一些常見的術語考慮因素需要牢記。例如,通常將任何 Attr 節點稱為 attribute,將 DOM 節點陣列稱為 nodeList。您會發現這些術語和其他將在整個文件中介紹和使用。
DOM 介面
本指南是關於您用來操作 DOM 層次結構的物件和實際事物。在許多方面,理解這些工作方式可能會令人困惑。例如,表示 HTML form 元素的物件從 HTMLFormElement 介面獲取其 name 屬性,但從 HTMLElement 介面獲取其 className 屬性。在這兩種情況下,您想要的屬性都在該表單物件中。
但物件與其在 DOM 中實現的介面之間的關係可能會令人困惑,因此本節嘗試簡要介紹 DOM 規範中的實際介面及其可用方式。
介面和物件
許多物件實現了多個不同的介面。例如,表格物件實現了專門的 HTMLTableElement 介面,其中包含諸如 createCaption 和 insertRow 等方法。但由於它也是一個 HTML 元素,因此 table 實現了 DOM Element 參考章節中描述的 Element 介面。最後,由於 HTML 元素在 DOM 的眼中也是構成 HTML 或 XML 頁面物件模型的節點樹中的一個節點,因此表格物件也實現了更基本的 Node 介面,而 Element 則派生自該介面。
當您獲得對 table 物件的引用時,例如在以下示例中,您通常會在物件上交替使用所有這三個介面,也許您自己並不知道。
const table = document.getElementById("table");
const tableAttrs = table.attributes; // Node/Element interface
for (let i = 0; i < tableAttrs.length; i++) {
// HTMLTableElement interface: border attribute
if (tableAttrs[i].nodeName.toLowerCase() === "border") {
table.border = "1";
}
}
// HTMLTableElement interface: summary attribute
table.summary = "note: increased border";
DOM 中的核心介面
本節列出了 DOM 中一些最常用的介面。其目的不是在這裡描述這些 API 的功能,而是讓您瞭解在使用 DOM 時經常會遇到的方法和屬性型別。這些通用 API 在本書結尾的 DOM 示例 章節中更長的示例中使用。
document 和 window 物件是您在 DOM 程式設計中通常最常使用的介面物件。簡單來說,window 物件代表類似瀏覽器的東西,而 document 物件是文件本身的根節點。Element 繼承自通用 Node 介面,這兩個介面一起提供了您在單個元素上使用的許多方法和屬性。這些元素也可能具有特定於處理這些元素所持資料型別的介面,如上一節中 table 物件的示例。
以下是使用 DOM 進行 Web 和 XML 頁面指令碼編寫的一些常見 API 的簡要列表。
示例
設定文字內容
此示例使用一個包含 <textarea> 和兩個 <button> 元素的 <div> 元素。當用戶單擊第一個按鈕時,我們在 <textarea> 中設定一些文字。當用戶單擊第二個按鈕時,我們清除文字。我們使用
Document.querySelector()來訪問<textarea>和按鈕EventTarget.addEventListener()來監聽按鈕點選Node.textContent來設定和清除文字。
HTML
<div class="container">
<textarea class="story"></textarea>
<button id="set-text" type="button">Set text content</button>
<button id="clear-text" type="button">Clear text content</button>
</div>
CSS
.container {
display: flex;
gap: 0.5rem;
flex-direction: column;
}
button {
width: 200px;
}
JavaScript
const story = document.body.querySelector(".story");
const setText = document.body.querySelector("#set-text");
setText.addEventListener("click", () => {
story.textContent = "It was a dark and stormy night...";
});
const clearText = document.body.querySelector("#clear-text");
clearText.addEventListener("click", () => {
story.textContent = "";
});
結果
新增子元素
此示例使用一個包含 <div> 和兩個 <button> 元素的 <div> 元素。當用戶單擊第一個按鈕時,我們建立一個新元素並將其作為 <div> 的子元素新增。當用戶單擊第二個按鈕時,我們刪除子元素。我們使用
Document.querySelector()來訪問<div>和按鈕EventTarget.addEventListener()來監聽按鈕點選Document.createElement來建立元素Node.appendChild()來新增子元素Node.removeChild()來刪除子元素。
HTML
<div class="container">
<div class="parent">parent</div>
<button id="add-child" type="button">Add a child</button>
<button id="remove-child" type="button">Remove child</button>
</div>
CSS
.container {
display: flex;
gap: 0.5rem;
flex-direction: column;
}
button {
width: 100px;
}
div.parent {
border: 1px solid black;
padding: 5px;
width: 100px;
height: 100px;
}
div.child {
border: 1px solid red;
margin: 10px;
padding: 5px;
width: 80px;
height: 60px;
box-sizing: border-box;
}
JavaScript
const parent = document.body.querySelector(".parent");
const addChild = document.body.querySelector("#add-child");
addChild.addEventListener("click", () => {
// Only add a child if we don't already have one
// in addition to the text node "parent"
if (parent.childNodes.length > 1) {
return;
}
const child = document.createElement("div");
child.classList.add("child");
child.textContent = "child";
parent.appendChild(child);
});
const removeChild = document.body.querySelector("#remove-child");
removeChild.addEventListener("click", () => {
const child = document.body.querySelector(".child");
parent.removeChild(child);
});
結果
規範
| 規範 |
|---|
| DOM 標準 |