操作文档

在编写网页和应用程序时,您最常想要做的事情之一是以某种方式操作文档结构。 这通常通过使用文档对象模型 (DOM) 来完成,DOM 是用于控制 HTML 和样式信息的 API 集合,它大量使用了 Document 对象。 在本文中,我们将详细介绍如何使用 DOM,以及其他一些有趣的 API,这些 API 可以以有趣的方式改变您的环境。

先决条件 对 HTML、CSS 和 JavaScript 的基本了解——包括 JavaScript 对象。
目标 熟悉核心 DOM API,以及与 DOM 和文档操作相关的其他 API。

网页浏览器的重要部分

Web 浏览器是非常复杂的软件,有很多活动部件,其中许多不能由使用 JavaScript 的 Web 开发人员控制或操作。 您可能认为这些限制是一件坏事,但浏览器被锁定是有充分理由的,主要原因是安全。 想象一下,如果一个网站可以访问您存储的密码或其他敏感信息,并以您的身份登录网站?

尽管存在这些限制,Web API 仍然为我们提供了访问许多功能,使我们能够使用网页做很多事情。 您会在代码中经常引用一些非常明显的片段——请考虑以下图表,它代表了浏览器中直接参与查看网页的主要部分

Important parts of web browser; the document is the web page. The window includes the entire document and also the tab. The navigator is the browser, which includes the window (which includes the document) and all other windows.

  • 窗口是加载网页的浏览器选项卡;这在 JavaScript 中由 Window 对象表示。 使用此对象上可用的方法,您可以执行诸如返回窗口大小(参见 Window.innerWidthWindow.innerHeight)、操作加载到该窗口的文档、在客户端存储特定于该文档的数据(例如使用本地数据库或其他存储机制)、将 事件处理程序 附加到当前窗口等等。
  • 导航器代表浏览器的状态和身份(即用户代理),因为它存在于网络上。 在 JavaScript 中,这由 Navigator 对象表示。 您可以使用此对象检索诸如用户首选语言、来自用户网络摄像头的媒体流等内容。
  • 文档(由浏览器中的 DOM 表示)是加载到窗口中的实际页面,在 JavaScript 中由 Document 对象表示。 您可以使用此对象返回和操作构成文档的 HTML 和 CSS 中的信息,例如获取对 DOM 中元素的引用、更改其文本内容、对其应用新样式、创建新元素并将它们添加到当前元素作为子元素,甚至完全删除它。

在本文中,我们将主要关注操作文档,但我们也会展示一些其他有用的内容。

文档对象模型

您每个浏览器选项卡中当前加载的文档都由文档对象模型表示。 这是一个由浏览器创建的“树状结构”表示,它使 HTML 结构能够被编程语言轻松访问——例如,浏览器本身使用它在渲染页面时将样式和其他信息应用于正确的元素,并且像您这样的开发人员可以在页面渲染后使用 JavaScript 操作 DOM。

我们在 dom-example.html (也可以在线查看) 创建了一个简单的示例页面。 尝试在您的浏览器中打开它——它是一个非常简单的页面,包含一个 <section> 元素,您可以在其中找到一个图像,以及一个包含链接的段落。 HTML 源代码如下所示

html
<!doctype html>
<html lang="en-US">
  <head>
    <meta charset="utf-8" />
    <title>Simple DOM example</title>
  </head>
  <body>
    <section>
      <img
        src="dinosaur.png"
        alt="A red Tyrannosaurus Rex: A two legged dinosaur standing upright like a human, with small arms, and a large head with lots of sharp teeth." />
      <p>
        Here we will add a link to the
        <a href="https://www.mozilla.org/">Mozilla homepage</a>
      </p>
    </section>
  </body>
</html>

另一方面,DOM 看起来像这样

Tree structure representation of Document Object Model: The top node is the doctype and HTML element. Child nodes of the HTML include head and body. Each child element is a branch. All text, even white space, is shown as well.

注意: 此 DOM 树图使用 Ian Hickson 的 实时 DOM 查看器 创建。

树中的每个条目都称为节点。 您可以在上面的图中看到,一些节点表示元素(标识为 HTMLHEADMETA 等),而其他节点表示文本(标识为 #text)。 还存在 其他类型的节点,但这些是您会遇到的主要节点。

节点也以它们在树中相对于其他节点的位置来指代

  • 根节点:树中最顶部的节点,在 HTML 的情况下始终是 HTML 节点(其他标记词汇表,如 SVG 和自定义 XML 将具有不同的根元素)。
  • 子节点直接位于另一个节点内的节点。 例如,IMG 是上面示例中 SECTION 的子节点。
  • 后代节点任何地方位于另一个节点内的节点。 例如,IMG 是上面示例中 SECTION 的子节点,它也是一个后代。 IMG 不是 BODY 的子节点,因为它在树中低于 BODY 两级,但它是 BODY 的后代。
  • 父节点:在其中包含另一个节点的节点。 例如,BODY 是上面示例中 SECTION 的父节点。
  • 兄弟节点:位于 DOM 树中同一级别的节点。 例如,IMGP 是上面示例中的兄弟节点。

在使用 DOM 之前熟悉这些术语很有用,因为您会遇到的一些代码术语使用它们。 如果您学习过 CSS(例如,后代选择器、子选择器),您可能也已经遇到过它们。

主动学习:基本 DOM 操作

为了开始学习 DOM 操作,让我们从一个实际示例开始。

  1. 获取 dom-example.html 页面 和与其相关的 图像 的本地副本。
  2. 在结束的 </body> 标记之前添加一个 <script></script> 元素。
  3. 要操作 DOM 内部的元素,您首先需要选择它并将其引用存储在一个变量中。 在您的脚本元素中,添加以下行
    js
    const link = document.querySelector("a");
    
  4. 现在我们已经将元素引用存储在变量中,我们可以开始使用可用的属性和方法对其进行操作(这些属性和方法在接口上定义,例如 HTMLAnchorElement<a> 元素的情况下,其更通用的父接口 HTMLElement,以及 Node——它表示 DOM 中的所有节点)。 首先,让我们通过更新 Node.textContent 属性的值来更改链接内的文本。 在前一行下方添加以下行
    js
    link.textContent = "Mozilla Developer Network";
    
  5. 我们还应该更改链接指向的 URL,以便在单击它时不会转到错误的位置。 添加以下行,同样位于底部
    js
    link.href = "https://mdn.org.cn";
    

请注意,与 JavaScript 中的许多事情一样,有许多方法可以选择元素并将对其的引用存储在一个变量中。 Document.querySelector() 是推荐的现代方法。 它很方便,因为它允许您使用 CSS 选择器选择元素。 上面的 querySelector() 调用将匹配出现在文档中的第一个 <a> 元素。 如果您想匹配并对多个元素执行操作,您可以使用 Document.querySelectorAll(),它匹配文档中与选择器匹配的每个元素,并将对其的引用存储在称为 数组 类对象的 NodeList 中。

还有一些较旧的方法可用于获取元素引用,例如

  • Document.getElementById(),它选择具有给定 id 属性值的元素,例如 <p id="myId">My paragraph</p>。 ID 作为参数传递给函数,即 const elementRef = document.getElementById('myId')
  • Document.getElementsByTagName(),它返回一个数组类对象,其中包含页面上给定类型的所有元素,例如 <p><a> 等。 元素类型作为参数传递给函数,即 const elementRefArray = document.getElementsByTagName('p')

这两个方法在旧浏览器中的效果比 querySelector() 等现代方法更好,但不如它们方便。 看看您还能找到哪些其他方法!

创建和放置新节点

上面已经让您对可以做些什么有了一些了解,但让我们更进一步,看看如何创建新元素。

  1. 回到当前示例,让我们先获取对 <section> 元素的引用——在现有脚本的底部添加以下代码(对其他行也执行相同的操作)
    js
    const sect = document.querySelector("section");
    
  2. 现在,让我们使用 Document.createElement() 创建一个新的段落,并像以前一样赋予它一些文本内容
    js
    const para = document.createElement("p");
    para.textContent = "We hope you enjoyed the ride.";
    
  3. 您现在可以使用 Node.appendChild() 将新段落追加到该部分的末尾
    js
    sect.appendChild(para);
    
  4. 最后,对于这部分,让我们在链接所在的段落中添加一个文本节点,以很好地结束句子。 首先,我们将使用 Document.createTextNode() 创建文本节点
    js
    const text = document.createTextNode(
      " — the premier source for web development knowledge.",
    );
    
  5. 现在我们将获取对链接所在的段落的引用,并将文本节点追加到它
    js
    const linkPara = document.querySelector("p");
    linkPara.appendChild(text);
    

这些就是将节点添加到 DOM 所需的大部分内容——您在构建动态界面时将大量使用这些方法(我们将在后面介绍一些示例)。

移动和删除元素

有时您可能想要移动节点,或完全从 DOM 中删除它们。 这是完全可能的。

如果我们想将包含链接的段落移动到该部分的底部,我们可以这样做

js
sect.appendChild(linkPara);

这将段落移动到该部分的底部。 您可能认为它会创建第二个副本,但事实并非如此——linkPara 是该段落唯一副本的引用。 如果您想创建一个副本并将其添加,则需要使用 Node.cloneNode()

删除节点也很简单,至少当您有要删除节点及其父节点的引用时。 在我们当前的情况下,我们只需使用 Node.removeChild(),如下所示

js
sect.removeChild(linkPara);

当您只想根据对节点本身的引用删除节点时,这很常见,您可以使用 Element.remove()

js
linkPara.remove();

此方法在旧浏览器中不受支持。 它们没有方法告诉节点删除自身,因此您需要执行以下操作。

js
linkPara.parentNode.removeChild(linkPara);

尝试将上述代码行添加到您的代码中。

操作样式

可以使用多种方法通过 JavaScript 操作 CSS 样式。

首先,您可以使用 Document.stylesheets 获取附加到文档的所有样式表列表,该方法返回包含 CSSStyleSheet 对象的类似数组的对象。然后,您可以根据需要添加或删除样式。但是,我们不会详细介绍这些功能,因为它们是一种比较古老且难以操作样式的方法。还有更简单的方法。

第一个方法是直接将内联样式添加到您想要动态设置样式的元素。这可以通过 HTMLElement.style 属性完成,该属性包含文档中每个元素的内联样式信息。您可以设置此对象的属性以直接更新元素样式。

  1. 例如,尝试将以下几行添加到我们的示例中
    js
    para.style.color = "white";
    para.style.backgroundColor = "black";
    para.style.padding = "10px";
    para.style.width = "250px";
    para.style.textAlign = "center";
    
  2. 重新加载页面,您将看到样式已应用于段落。如果您在浏览器的 页面检查器/DOM 检查器 中查看该段落,您将看到这些行确实向文档添加了内联样式。
    html
    <p
      style="color: white; background-color: black; padding: 10px; width: 250px; text-align: center;">
      We hope you enjoyed the ride.
    </p>
    

注意:请注意,CSS 样式的 JavaScript 属性版本如何使用 小驼峰命名法 编写,而 CSS 版本则使用连字符 (短横线命名法)(例如 backgroundColorbackground-color)。确保不要将它们混淆,否则将无法正常工作。

还有另一种常见的动态操作文档样式的方法,我们现在将介绍。

  1. 删除您之前添加到 JavaScript 的五行代码。
  2. 在您的 HTML <head> 中添加以下内容
    html
    <style>
      .highlight {
        color: white;
        background-color: black;
        padding: 10px;
        width: 250px;
        text-align: center;
      }
    </style>
    
  3. 现在我们将转向一个非常有用的通用 HTML 操作方法——Element.setAttribute()——它接受两个参数,即您想要在元素上设置的属性和您想要设置的值。在本例中,我们将为我们的段落设置一个名为 highlight 的类名
    js
    para.setAttribute("class", "highlight");
    
  4. 刷新页面,您将看到没有任何变化——CSS 仍然应用于段落,但这次是通过为它指定一个类来实现的,该类被我们的 CSS 规则选中,而不是作为内联 CSS 样式。

您可以选择哪种方法;两种方法都有各自的优点和缺点。第一种方法设置起来更简单,适用于简单的用途,而第二种方法更加纯粹(不混合 CSS 和 JavaScript,没有内联样式,这些都被视为不良做法)。当您开始构建更大更复杂的应用程序时,您可能会开始更多地使用第二种方法,但这完全取决于您。

到目前为止,我们还没有真正做任何有用的事情!使用 JavaScript 创建静态内容没有意义——您也可以直接将其写入 HTML 中而不使用 JavaScript。它比 HTML 更复杂,而且使用 JavaScript 创建内容还存在其他问题(例如搜索引擎无法读取)。

在下一节中,我们将研究 DOM API 的更实际用途。

注意:您可以在 GitHub 上找到我们 dom-example.html 的完成版本 演示(也可以在线查看)。

主动学习:动态购物清单

在本挑战中,我们想制作一个简单的购物清单示例,允许您使用表单输入和按钮动态地向列表添加项目。当您在输入框中添加项目并按下按钮时

  • 该项目应该出现在列表中。
  • 每个项目都应该有一个按钮,可以按下该按钮将该项目从列表中删除。
  • 输入框应该被清空并获得焦点,以便您可以输入另一个项目。

完成的演示应该看起来像这样

Demo layout of a shopping list. A 'my shopping list' header followed by 'Enter a new item' with an input field and 'add item' button. The list of already added items is below, each with a corresponding delete button.

要完成练习,请按照以下步骤操作,并确保列表的行为如上所述。

  1. 首先,下载我们 shopping-list.html 起始文件的副本,并将其复制到某个地方。您会看到它有一些最小的 CSS、一个带有标签、输入框和按钮的 div、一个空的列表和 <script> 元素。您将在脚本中进行所有添加操作。
  2. 创建三个变量,它们分别保存对列表 (<ul>)、<input><button> 元素的引用。
  3. 创建一个 函数,该函数将在按钮被点击时运行。
  4. 在函数主体内部,首先将输入元素的当前 存储在一个变量中。
  5. 接下来,通过将输入元素的值设置为一个空字符串——''——来清空它。
  6. 创建三个新元素——一个列表项 (<li>)、<span><button>,并将它们存储在变量中。
  7. 将 span 和按钮作为子元素附加到列表项中。
  8. 将 span 的文本内容设置为之前保存的输入元素的值,并将按钮的文本内容设置为“删除”。
  9. 将列表项作为子元素附加到列表中。
  10. 向删除按钮附加一个事件处理程序,以便在点击时,它将删除整个列表项 (<li>...</li>)。
  11. 最后,使用 focus() 方法使输入元素获得焦点,以便输入下一个购物清单项目。

注意:如果您真的卡住了,请查看我们 完成的购物清单也可以在线查看)。

总结

我们已经完成了对文档和 DOM 操作的研究。此时,您应该了解 Web 浏览器在控制文档和用户的 Web 体验的其他方面时所涉及的重要部分。最重要的是,您应该了解什么是文档对象模型以及如何操作它以创建有用的功能。

另请参阅

您可以使用更多功能来操作文档。查看我们的一些参考资料,看看您能发现什么。

(查看我们的 Web API 索引,以获取 MDN 上记录的完整 Web API 列表!)