构建和更新 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);