文件物件模型 (DOM)

文件物件模型DOM)透過在記憶體中表示文件的結構(例如表示網頁的 HTML),將網頁連線到指令碼或程式語言。通常它指 JavaScript,儘管將 HTML、SVG 或 XML 文件建模為物件並不是核心 JavaScript 語言的一部分。

DOM 以邏輯樹的形式表示文件。樹的每個分支都以節點結束,每個節點都包含物件。DOM 方法允許以程式設計方式訪問樹。透過它們,你可以更改文件的結構、樣式或內容。

節點還可以附加事件處理程式。一旦事件觸發,事件處理程式就會執行。

概念與用法

文件物件模型 (DOM) 是用於 Web 文件的程式設計介面。它表示頁面,以便程式可以更改文件結構、樣式和內容。DOM 將文件表示為節點和物件;這樣,程式語言就可以與頁面互動。

網頁是一個文件,可以顯示在瀏覽器視窗中,也可以作為 HTML 原始碼。在這兩種情況下,它都是相同的文件,但文件物件模型 (DOM) 表示允許對其進行操作。作為網頁的面向物件表示,它可以用指令碼語言(例如 JavaScript)進行修改。

例如,DOM 指定此程式碼片段中的 querySelectorAll 方法必須返回文件中所有 <p> 元素的列表

js
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 物件,實現用於訪問 HTML 表格的 HTMLTableElement DOM 介面的任何 table 物件等等,都是物件。

DOM 是使用多個協同工作的 API 構建的。核心 DOM 定義了描述任何文件及其內部實體的實體。其他 API 會根據需要擴充套件它,為 DOM 新增新功能和能力。例如,HTML DOM API 為核心 DOM 添加了對錶示 HTML 文件的支援,SVG API 添加了對錶示 SVG 文件的支援。

什麼是 DOM 樹?

DOM 樹是一種樹形結構,其節點代表 HTML 或 XML 文件的內容。每個 HTML 或 XML 文件都有一個 DOM 樹表示。例如,考慮以下文件

html
<html lang="en">
  <head>
    <title>My Document</title>
  </head>
  <body>
    <h1>Header</h1>
    <p>Paragraph</p>
  </body>
</html>

它有一個看起來像這樣的 DOM 樹

The DOM as a tree-like representation of a document that has a root and node elements containing content

儘管上面的樹與上面文件的 DOM 樹相似,但它們並不相同,因為實際的 DOM 樹保留了空格

當網路瀏覽器解析 HTML 文件時,它會構建一個 DOM 樹,然後使用它來顯示文件。

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
# 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> 元素中內聯,還是包含在網頁中,您都可以立即開始使用 documentwindow 物件的 API,以操作文件本身或網頁中的各種元素(文件的後代元素)。您的 DOM 程式設計可能像以下示例一樣簡單,它使用 console.log() 函式在控制檯上顯示一條訊息

html
<body onload="console.log('Welcome to my home page!');">
  …
</body>

由於通常不建議將頁面結構(用 HTML 編寫)和 DOM 操作(用 JavaScript 編寫)混合在一起,因此 JavaScript 部分將在此處分組在一起,並與 HTML 分開。

例如,以下函式建立了一個新的 h1 元素,向該元素新增文字,然後將其新增到文件的樹中

html
<html lang="en">
  <head> </head>
  <body>
    <script>
      // 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>
  </body>
</html>

DOM 介面

以下是 DOM 規範定義的所有介面

本指南介紹了您可用於操作 DOM 層次結構的物件和實際“事物”。在很多方面,理解它們如何工作可能會令人困惑。例如,表示 HTML form 元素的物件從 HTMLFormElement 介面獲取其 name 屬性,但從 HTMLElement 介面獲取其 className 屬性。在這兩種情況下,您想要的屬性都在該表單物件中。

但物件及其在 DOM 中實現的介面之間的關係可能令人困惑,因此本節試圖簡單介紹 DOM 規範中的實際介面以及它們如何可用。

介面與物件

許多物件實現了多個不同的介面。例如,表格物件實現了一個專門的 HTMLTableElement 介面,其中包含 createCaptioninsertRow 等方法。但由於它也是一個 HTML 元素,table 實現了 DOM Element 參考章節中描述的 Element 介面。最後,由於就 DOM 而言,HTML 元素也是構成 HTML 或 XML 頁面物件模型的節點樹中的一個節點,因此表格物件還實現了更基本的 Node 介面,Element 就是從該介面派生出來的。

當您獲得對 table 物件的引用時,如以下示例所示,您通常會在該物件上互換使用這三個介面,也許您並不知道。

js
const table = document.getElementById("table");
const tableAttrs = table.attributes; // Node/Element interface
for (const attr of tableAttrs) {
  // HTMLTableElement interface: border attribute
  if (attr.nodeName.toLowerCase() === "border") {
    table.border = "1";
  }
}
// HTMLTableElement interface: summary attribute
table.summary = "note: increased border";

基本資料型別

本頁面嘗試用簡單的術語描述各種物件和型別。但您應該注意 API 中傳遞的許多不同資料型別。

注意:由於絕大多數使用 DOM 的程式碼都圍繞著操作 HTML 文件,因此通常將 DOM 中的節點稱為元素,儘管嚴格來說並非每個節點都是元素。

下表簡要描述了這些資料型別。

資料型別(介面) 描述
Document 當成員返回 document 型別的物件(例如,元素的 ownerDocument 屬性返回其所屬的 document)時,此物件就是根 document 物件本身。DOM document 參考章節描述了 document 物件。
Node 文件中的每個物件都是某種型別的節點。在 HTML 文件中,一個物件可以是元素節點,也可以是文字節點或屬性節點。
Element element 型別基於 node。它指的是由 DOM API 成員返回的 element 型別元素或節點。我們不說,例如,document.createElement() 方法返回對 node 的物件引用,我們只說此方法返回剛在 DOM 中建立的 elementelement 物件實現了 DOM Element 介面以及更基本的 Node 介面,這兩個介面都包含在本參考資料中。在 HTML 文件中,元素透過 HTML DOM API 的 HTMLElement 介面以及描述特定型別元素功能的其他介面(例如,用於 <table> 元素的 HTMLTableElement)得到進一步增強。
Attr 當成員返回 attribute 時(例如,透過 createAttribute() 方法),它是一個物件引用,公開了一個特殊(儘管很小)的屬性介面。屬性是 DOM 中的節點,就像元素一樣,儘管您可能很少將它們這樣使用。

還有一些常見的術語需要記住。例如,通常將任何 Attr 節點稱為 attribute,並將 DOM 節點陣列稱為 nodeList。您會發現這些術語和其他術語將在整個文件中引入和使用。

documentwindow 物件是您在 DOM 程式設計中通常最常使用的介面物件。簡單來說,window 物件代表類似瀏覽器的事物,而 document 物件是文件本身的根。Element 繼承自通用的 Node 介面,這兩個介面共同提供了您在單個元素上使用的許多方法和屬性。這些元素也可能有專門的介面來處理它們所持有的資料型別,如前一節中 table 物件的示例所示。

廢棄的 DOM 介面

文件物件模型已經高度簡化。為了實現這一點,DOM Level 3 或更早規範中的以下介面已被移除。它們不再可供 Web 開發人員使用。

  • DOMConfiguration
  • DOMErrorHandler
  • DOMImplementationList
  • DOMImplementationRegistry
  • DOMImplementationSource
  • DOMLocator
  • DOMObject
  • DOMSettableTokenList
  • DOMUserData
  • ElementTraversal
  • Entity
  • EntityReference
  • NameList
  • Notation
  • TypeInfo
  • UserDataHandler

HTML DOM

包含 HTML 的文件使用 Document 介面進行描述,該介面由 HTML 規範擴充套件,以包含各種 HTML 特定的功能。特別是,Element 介面被增強為 HTMLElement 和各種子類,每個子類代表一個(或一組密切相關)元素。

HTML DOM API 提供了對各種瀏覽器功能的訪問,例如標籤和視窗、CSS 樣式和樣式表、瀏覽器歷史記錄等。這些介面將在 HTML DOM API 文件中進一步討論。

SVG DOM

同樣,包含 SVG 的文件也使用 Document 介面進行描述,該介面由 SVG 規範擴充套件,以包含各種 SVG 特定的功能。特別是,Element 介面被增強為 SVGElement 和各種子類,每個子類代表一個元素或一組密切相關的元素。這些介面將在 SVG API 文件中進一步討論。

示例

設定文字內容

此示例使用一個 <div> 元素,其中包含一個 <textarea> 和兩個 <button> 元素。當用戶單擊第一個按鈕時,我們在 <textarea> 中設定一些文字。當用戶單擊第二個按鈕時,我們清除文字。我們使用

HTML

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

css
.container {
  display: flex;
  gap: 0.5rem;
  flex-direction: column;
}

button {
  width: 200px;
}

JavaScript

js
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> 元素,其中包含一個 <div> 和兩個 <button> 元素。當用戶點選第一個按鈕時,我們建立一個新元素並將其作為 <div> 的子元素新增。當用戶點選第二個按鈕時,我們移除子元素。我們使用

HTML

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

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

js
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 樹?文件的標題,並寫兩個段落而不是一個。以下指令碼可以完成此工作

HTML

html
<html lang="en">
  <head>
    <title>My Document</title>
  </head>
  <body>
    <input type="button" value="Change this document." />
    <h2>Header</h2>
    <p>Paragraph</p>
  </body>
</html>

JavaScript

js
document.querySelector("input").addEventListener("click", () => {
  // document.getElementsByTagName("h2") returns a NodeList of the <h2>
  // elements in the document, and the first is number 0:
  const header = document.getElementsByTagName("h2").item(0);

  // The firstChild of the header is a Text node:
  header.firstChild.data = "A dynamic document";

  // Now header is "A dynamic document".

  // Access the first paragraph
  const para = document.getElementsByTagName("p").item(0);
  para.firstChild.data = "This is the first paragraph.";

  // Create a new Text node for the second paragraph
  const newText = document.createTextNode("This is the second paragraph.");

  // Create a new Element to be the second paragraph
  const newElement = document.createElement("p");

  // Put the text in the paragraph
  newElement.appendChild(newText);

  // Put the paragraph on the end of the document by appending it to
  // the body (which is the parent of para)
  para.parentNode.appendChild(newElement);
});

建立一棵樹

您也可以完全用 JavaScript 在什麼是 DOM 樹?中建立這棵樹。

js
const root = document.createElement("html");
root.lang = "en";

const head = document.createElement("head");
const title = document.createElement("title");
title.appendChild(document.createTextNode("My Document"));
head.appendChild(title);

const body = document.createElement("body");
const header = document.createElement("h1");
header.appendChild(document.createTextNode("Header"));
const paragraph = document.createElement("p");
paragraph.appendChild(document.createTextNode("Paragraph"));
body.appendChild(header);
body.appendChild(paragraph);

root.appendChild(head);
root.appendChild(body);

事件傳播

此示例以一種非常簡單的方式演示了事件在 DOM 中如何觸發和處理。當此 HTML 文件的 BODY 載入時,一個事件監聽器會註冊到 TABLE 的頂行。該事件監聽器透過執行函式 stopEvent 來處理事件,該函式會更改表格底部單元格中的值。

但是,stopEvent 也呼叫了一個事件物件方法 event.stopPropagation,該方法阻止事件進一步向上冒泡到 DOM 中。請注意,表格本身有一個 onclick 事件處理程式,當表格被點選時應該顯示一條訊息。但 stopEvent 方法已停止傳播,因此在表格中的資料更新後,事件階段實際上已經結束,並顯示一個警告框以確認這一點。

html
<table id="t-daddy">
  <tr id="tbl1">
    <td id="c1">one</td>
  </tr>
  <tr>
    <td id="c2">two</td>
  </tr>
</table>
css
#t-daddy {
  border: 1px solid red;
}

#c1 {
  background-color: pink;
}
js
function stopEvent(event) {
  const c2 = document.getElementById("c2");
  c2.textContent = "hello";

  // this ought to keep t-daddy from getting the click.
  event.stopPropagation();
  console.log("event propagation halted.");
}

const elem = document.getElementById("tbl1");
elem.addEventListener("click", stopEvent);

document.getElementById("t-daddy").addEventListener("click", () => {
  console.log("t-daddy clicked");
});

顯示事件物件屬性

此示例使用 DOM 方法在一個表格中顯示 onload event 物件的所有屬性及其值。它還展示了一種有用的技術,即使用 for...in 迴圈遍歷物件的屬性以獲取它們的值。

事件物件的屬性在不同瀏覽器之間差異很大,WHATWG DOM 標準列出了標準屬性,但許多瀏覽器已大大擴充套件了這些屬性。

將以下程式碼放入一個空白文字檔案並將其載入到各種瀏覽器中,您會驚歎於不同數量和名稱的屬性。您可能還想在頁面中新增一些元素,並從不同的事件處理程式呼叫此函式。

html
<h1>Properties of the DOM <span id="eventType"></span> Event Object</h1>
css
table {
  border-collapse: collapse;
}
thead {
  font-weight: bold;
}
td {
  padding: 2px 10px;
}

.odd {
  background-color: #efdfef;
}
.even {
  background-color: white;
}
js
function showEventProperties(e) {
  function addCell(row, text) {
    const cell = row.insertCell(-1);
    cell.appendChild(document.createTextNode(text));
  }

  const event = e || window.event;
  document.getElementById("eventType").textContent = event.type;

  const table = document.createElement("table");
  const thead = table.createTHead();
  let row = thead.insertRow(-1);
  const labelList = ["#", "Property", "Value"];
  const len = labelList.length;

  for (let i = 0; i < len; i++) {
    addCell(row, labelList[i]);
  }

  const tbody = document.createElement("tbody");
  table.appendChild(tbody);

  for (const p in event) {
    row = tbody.insertRow(-1);
    row.className = row.rowIndex % 2 ? "odd" : "even";
    addCell(row, row.rowIndex);
    addCell(row, p);
    addCell(row, event[p]);
  }

  document.body.appendChild(table);
}

showEventProperties(event);

規範

規範
DOM

另見