構建和更新 DOM 樹

本文概述了一些強大的、基礎的 DOM Level 1 方法以及如何從 JavaScript 中使用它們。您將學習如何動態建立、訪問、控制和刪除 HTML 元素。此處介紹的 DOM 方法並非僅限於 HTML;它們也適用於 XML。此處提供的演示在任何現代瀏覽器中都能正常工作。

注意:此處介紹的 DOM 方法是 Document Object Model (Core) Level 1 規範的一部分。DOM Level 1 包括用於通用文件訪問和操作的方法 (DOM 1 Core) 以及特定於 HTML 文件的方法 (DOM 1 HTML)。

動態建立 HTML 表格

示例

在此示例中,當點選按鈕時,我們向頁面新增一個新表格。

HTML

html
<input type="button" value="Generate a table" />

JavaScript

js
function generateTable() {
  // creates a <table> element and a <tbody> element
  const tbl = document.createElement("table");
  const tblBody = document.createElement("tbody");

  // creating all cells
  for (let i = 0; i < 2; i++) {
    // creates a table row
    const row = document.createElement("tr");

    for (let j = 0; j < 2; j++) {
      // Create a <td> element and a text node, make the text
      // node the contents of the <td>, and put the <td> at
      // the end of the table row
      const cell = document.createElement("td");
      const cellText = document.createTextNode(`cell in row ${i}, column ${j}`);
      cell.appendChild(cellText);
      row.appendChild(cell);
    }

    // add the row to the end of the table body
    tblBody.appendChild(row);
  }

  // put the <tbody> in the <table>
  tbl.appendChild(tblBody);
  // appends <table> into <body>
  document.body.appendChild(tbl);
  // sets the border attribute of tbl to '2'
  tbl.setAttribute("border", "2");
}

document
  .querySelector("input[type='button']")
  .addEventListener("click", generateTable);

結果

解釋

注意我們建立元素和文字節點時的順序

  1. 首先,我們建立了 <table> 元素。
  2. 接下來,我們建立了 <tbody> 元素,它是 <table> 元素的子元素。
  3. 接下來,我們使用迴圈建立 <tr> 元素,它們是 <tbody> 元素的子元素。
  4. 對於每個 <tr> 元素,我們使用迴圈建立 <td> 元素,它們是 <tr> 元素的子元素。
  5. 對於每個 <td> 元素,我們建立了包含表格單元格文字的文字節點。

一旦我們建立了 <table><tbody><tr><td> 元素,然後建立了文字節點,我們就以相反的順序將每個物件附加到其父節點。

  1. 首先,我們使用以下方法將每個文字節點附加到其父 <td> 元素:

    js
    cell.appendChild(cellText);
    
  2. 接下來,我們使用以下方法將每個 <td> 元素附加到其父 <tr> 元素:

    js
    row.appendChild(cell);
    
  3. 接下來,我們使用以下方法將每個 <tr> 元素附加到父 <tbody> 元素:

    js
    tblBody.appendChild(row);
    
  4. 接下來,我們使用以下方法將 <tbody> 元素附加到其父 <table> 元素:

    js
    tbl.appendChild(tblBody);
    
  5. 接下來,我們使用以下方法將 <table> 元素附加到其父 <body> 元素:

    js
    document.body.appendChild(tbl);
    

記住這個技巧。在 W3C DOM 程式設計中,您會經常使用它。首先,您從上到下建立元素;然後,您從下到上將子節點附加到父節點。

這是 JavaScript 程式碼生成的 HTML 標記

html
<table border="2">
  <tbody>
    <tr>
      <td>cell is row 0 column 0</td>
      <td>cell is row 0 column 1</td>
    </tr>
    <tr>
      <td>cell is row 1 column 0</td>
      <td>cell is row 1 column 1</td>
    </tr>
  </tbody>
</table>

這是程式碼生成的 <table> 元素及其子元素的 DOM 物件樹

How a DOM object tree is generated from the main element and its children

您可以使用一些 DOM 方法來構建此表格及其內部子元素。請記住您要建立的結構的樹模型;這將使編寫必要的程式碼更容易。在圖 1 的 <table> 樹中,<table> 元素有一個子元素:<tbody> 元素。<tbody> 有兩個子元素。每個 <tbody> 的子元素 (<tr>) 有兩個子元素 (<td>)。最後,每個 <td> 有一個子元素:一個文字節點。

設定段落的背景顏色

示例

在此示例中,當點選按鈕時,我們更改段落的背景顏色。

HTML

html
<body>
  <input type="button" value="Set paragraph background color" />
  <p>hi</p>
  <p>hello</p>
</body>

JavaScript

js
function setBackground() {
  // now, get all the p elements in the document
  const paragraphs = document.getElementsByTagName("p");

  // get the second paragraph from the list
  const secondParagraph = paragraphs[1];

  // set the inline style
  secondParagraph.style.background = "red";
}

document.querySelector("input").addEventListener("click", setBackground);

結果

解釋

getElementsByTagName(tagNameValue) 是任何 DOM Element 或根 Document 元素都可用的方法。呼叫時,它會返回一個包含所有匹配該標籤名的元素後代的陣列。列表的第一個元素位於陣列的位置 [0]

我們執行了以下步驟

  1. 首先,我們獲取文件中的所有 p 元素

    js
    const paragraphs = document.getElementsByTagName("p");
    
  2. 然後我們從 p 元素列表中獲取第二個段落元素

    js
    const secondParagraph = paragraphs[1];
    

    A paragraph element is added as a new sibling to an existing paragraph in a DOM tree

  3. 最後,我們使用 style 屬性將背景顏色設定為紅色,該屬性屬於 paragraph 物件

    js
    secondParagraph.style.background = "red";
    

使用 document.createTextNode("..") 建立 TextNodes

使用 document 物件呼叫 createTextNode 方法並建立您的文字節點。您只需要傳遞文字內容。返回值是一個代表文字節點的物件。

js
myTextNode = document.createTextNode("world");

這意味著您建立了一個型別為 TEXT_NODE (一段文字) 的節點,其文字資料為 "world",而 myTextNode 是指向此節點物件的引用。要將此文字插入到您的 HTML 頁面中,您需要將此文字節點作為另一個節點元素的子節點。

使用 appendChild(..) 插入元素

因此,透過呼叫 secondParagraph.appendChild(node_element),您將該元素作為第二個 <p> 元素的新的子元素。

js
secondParagraph.appendChild(myTextNode);

測試完此示例後,請注意單詞 hello 和 world 在一起:helloworld。因此,在視覺上,當您看到 HTML 頁面時,似乎兩個文字節點 hello 和 world 是一個節點,但請記住,在文件模型中,有兩個節點。第二個節點是型別為 TEXT_NODE 的新節點,它是第二個 <p> 標籤的第二個子節點。下圖顯示了最近建立的 Text Node 物件在文件樹中的位置。

Text nodes in a paragraph element as individual siblings in the DOM tree.

注意: createTextNode()appendChild() 是在單詞 helloworld 之間包含空格的簡單方法。另一個重要注意事項是,appendChild 方法會在最後一個子節點之後附加子節點,就像 world 這個詞被新增到 hello 之後一樣。所以,如果您想在 helloworld 之間附加一個文字節點,您需要使用 insertBefore 而不是 appendChild

使用 document 物件和 createElement(..) 方法建立新元素

您可以使用 createElement 建立新的 HTML 元素或任何您想要的元素。例如,如果您想建立一個新的 <p> 元素作為 <body> 元素的子元素,您可以使用上一個示例中的 myBody 並附加一個新元素節點。要建立一個節點,請呼叫 document.createElement("tagname")。例如:

js
myNewPTagNode = document.createElement("p");
myBody.appendChild(myNewPTagNode);

How a new node element is appended to the text node object inside the document tree

使用 removeChild(..) 方法刪除節點

節點可以被刪除。以下程式碼會從第二個 <p> 元素 secondParagraph 中刪除文字節點 myTextNode(包含單詞 "world")。

js
secondParagraph.removeChild(myTextNode);

文字節點 myTextNode(包含單詞 "world")仍然存在。以下程式碼將 myTextNode 附加到最近建立的 <p> 元素 myNewPTagNode

js
myNewPTagNode.appendChild(myTextNode);

修改後的物件樹的最終狀態如下所示

Creating and appending a new node element to the object tree text structure

動態建立表格

下圖顯示了示例中建立的表格的表格物件樹結構。

回顧 HTML 表格結構

The HTML table object tree structure after adding new node elements

建立元素節點並將它們插入到文件樹中

建立表格的基本步驟是:

  • 獲取 body 物件(document 物件的第一個項)。
  • 建立所有元素。
  • 最後,根據表格結構(如上圖所示)附加每個子節點。

注意:在指令碼的最後,有一行新程式碼。表格的 border 屬性是使用另一個 DOM 方法 setAttribute() 設定的。setAttribute() 有兩個引數:屬性名和屬性值。您可以使用 setAttribute 方法設定任何元素的任何屬性。

js
// get the reference for the body
const myBody = document.getElementsByTagName("body")[0];

// creates <table> and <tbody> elements
const myTable = document.createElement("table");
const myTableBody = document.createElement("tbody");

// creating all cells
for (let j = 0; j < 3; j++) {
  // creates a <tr> element
  const myCurrentRow = document.createElement("tr");

  for (let i = 0; i < 4; i++) {
    // creates a <td> element
    const myCurrentCell = document.createElement("td");
    // creates a Text Node
    const currentText = document.createTextNode(
      `cell is row ${j}, column ${i}`,
    );
    // appends the Text Node we created into the cell <td>
    myCurrentCell.appendChild(currentText);
    // appends the cell <td> into the row <tr>
    myCurrentRow.appendChild(myCurrentCell);
  }
  // appends the row <tr> into <tbody>
  myTableBody.appendChild(myCurrentRow);
}

// appends <tbody> into <table>
myTable.appendChild(myTableBody);
// appends <table> into <body>
myBody.appendChild(myTable);
// sets the border attribute of myTable to 2;
myTable.setAttribute("border", "2");

使用 DOM 和 CSS 操作表格

從表格中獲取文字節點

此示例引入了兩個新的 DOM 屬性。首先,它使用 childNodes 屬性獲取 myCell 的子節點列表。childNodes 列表包括所有子節點,無論它們的名稱或型別是什麼。與 getElementsByTagName() 類似,它返回一個節點列表。

區別在於 (a) getElementsByTagName() 只返回指定標籤名的元素;以及 (b) childNodes 包括任何級別的所有後代,而不僅僅是直接子節點。

一旦您獲得了返回的列表,就可以使用 [x] 方法來檢索所需的子項。此示例將表格第二行第二個單元格的文字節點儲存在 myCellText 中。

然後,為了在此示例中顯示結果,它會建立一個新的文字節點,其內容是 myCellText 的資料,並將其作為 <body> 元素的子節點附加。

注意:如果您的物件是文字節點,您可以使用 data 屬性並檢索節點的文字內容。

js
const myBody = document.getElementsByTagName("body")[0];
const myTable = myBody.getElementsByTagName("table")[0];
const myTableBody = myTable.getElementsByTagName("tbody")[0];
const myRow = myTableBody.getElementsByTagName("tr")[1];
const myCell = myRow.getElementsByTagName("td")[1];

// first item element of the childNodes list of myCell
const myCellText = myCell.childNodes[0];

// content of currentText is the data content of myCellText
const currentText = document.createTextNode(myCellText.data);
myBody.appendChild(currentText);

獲取屬性值

在 sample1 的末尾,有一個對 myTable 物件呼叫 setAttribute 的地方。此呼叫用於設定表格的 border 屬性。要檢索屬性的值,請使用 getAttribute 方法。

js
myTable.getAttribute("border");

透過更改樣式屬性隱藏列

一旦您將物件放入 JavaScript 變數中,您就可以直接設定 style 屬性。以下程式碼是一個修改版本,其中隱藏了第二列的每個單元格,並將第一列的每個單元格的背景更改為紅色。請注意,style 屬性是直接設定的。

js
const myBody = document.getElementsByTagName("body")[0];
const myTable = document.createElement("table");
const myTableBody = document.createElement("tbody");

for (let row = 0; row < 2; row++) {
  const myCurrentRow = document.createElement("tr");
  for (let col = 0; col < 2; col++) {
    const myCurrentCell = document.createElement("td");
    const currentText = document.createTextNode(`cell is: ${row}${col}`);
    myCurrentCell.appendChild(currentText);
    myCurrentRow.appendChild(myCurrentCell);
    // set the cell background color
    // if the column is 0. If the column is 1 hide the cell
    if (col === 0) {
      myCurrentCell.style.background = "red";
    } else {
      myCurrentCell.style.display = "none";
    }
  }
  myTableBody.appendChild(myCurrentRow);
}
myTable.appendChild(myTableBody);
myBody.appendChild(myTable);