DOM 脚本简介
在编写网页和应用程序时,最常见的操作之一就是以某种方式更改文档结构。这通常通过一组内置浏览器 API 来操作文档对象模型(DOM),以控制 HTML 和样式信息。在本文中,我们将向你介绍 DOM 脚本。
Web 浏览器重要组成部分
Web 浏览器是非常复杂的软件,包含许多活动部件,其中许多都无法由 Web 开发人员使用 JavaScript 进行控制或操作。你可能会认为这些限制是坏事,但浏览器之所以受限是有充分理由的,主要围绕安全性。想象一下,如果一个网站可以访问你存储的密码或其他敏感信息,并像你一样登录网站会怎样?
尽管存在限制,Web API 仍然为我们提供了许多功能,使我们能够对网页进行许多操作。在你的代码中,有一些非常明显的部分会经常引用 — 请看下面的图表,它表示浏览器中直接参与查看网页的主要部分
- window 表示加载网页的浏览器选项卡;这在 JavaScript 中由
Window
对象表示。使用此对象上可用的方法,你可以执行诸如返回窗口大小(参见Window.innerWidth
和 事件处理程序 附加到当前窗口等等操作。 - navigator 表示浏览器在 Web 上的状态和身份。在 JavaScript 中,这由
Navigator
对象表示。你可以使用此对象检索用户偏好语言、用户网络摄像头媒体流等信息。 - document(在浏览器中由 DOM 表示)是加载到窗口中的实际页面,在 JavaScript 中由
Document
对象表示。你可以使用此对象返回和操作构成文档的 HTML 和 CSS 信息,例如获取 DOM 中元素的引用、更改其文本内容、对其应用新样式、创建新元素并将其作为子元素添加到当前元素,甚至完全删除它。
在本文中,我们将主要关注文档操作,但我们也会展示一些其他有用的部分。
文档对象模型
让我们简要回顾一下文档对象模型(DOM),我们之前在本课程中也介绍过它。每个浏览器选项卡中当前加载的文档都由一个 DOM 表示。这是一个由浏览器创建的“树结构”表示,它使编程语言可以轻松访问 HTML 结构 — 例如,浏览器本身使用它在渲染页面时将样式和其他信息应用于正确的元素,而像你这样的开发人员可以在页面渲染后使用 JavaScript 操作 DOM。
我们创建了一个示例页面,位于 dom-example.html(也可以在线查看)。尝试在浏览器中打开它 — 这是一个非常简单的页面,包含一个 <section>
元素,其中有一个图像和一个带链接的段落。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 看起来是这样的
注意:此 DOM 树图是使用 Ian Hickson 的 Live DOM viewer 创建的。
树中的每个条目都称为一个节点。你可以从上面的图中看到,有些节点表示元素(标识为 HTML
、HEAD
、META
等),有些节点表示文本(标识为 #text
)。还有其他类型的节点,但这些是你会遇到的主要类型。
节点也按其在树中相对于其他节点的位置进行引用
- 根节点:树的顶部节点,在 HTML 中始终是
HTML
节点(SVG 和自定义 XML 等其他标记词汇将具有不同的根元素)。 - 子节点:直接位于另一个节点内部的节点。例如,在上面的示例中,
IMG
是SECTION
的子节点。 - 后代节点:任意位置位于另一个节点内部的节点。例如,在上面的示例中,
IMG
是SECTION
的子节点,它也是一个后代节点。IMG
不是BODY
的子节点,因为它在树中位于BODY
下两级,但它是BODY
的后代节点。 - 父节点:内部包含另一个节点的节点。例如,在上面的示例中,
BODY
是SECTION
的父节点。 - 同级节点:在 DOM 树中位于同一级别、同一父节点下的节点。例如,在上面的示例中,
IMG
和P
是同级节点。
在操作 DOM 之前熟悉这些术语很有用,因为你会遇到许多代码术语都使用它们。你也会在 CSS 中遇到它们(例如,后代选择器、子选择器)。
进行一些基本的 DOM 操作
为了开始学习 DOM 操作,让我们从一个实际示例开始。
-
获取 dom-example.html 页面 及其附带的图像的本地副本。
-
在结束的
</body>
标签之前添加一个<script></script>
元素。 -
要操作 DOM 中的元素,首先需要选择它并将其引用存储在变量中。在你的脚本元素中,添加以下行
jsconst link = document.querySelector("a");
-
现在我们已经将元素引用存储在一个变量中,我们可以开始使用它可用的属性和方法来操作它(这些属性和方法定义在像
HTMLAnchorElement
这样的接口上,对于<a>
元素,其更通用的父接口是HTMLElement
,以及代表 DOM 中所有节点的Node
)。首先,让我们通过更新Node.textContent
属性的值来更改链接内部的文本。在上一行下面添加以下行jslink.textContent = "Mozilla Developer Network";
-
我们还应该更改链接指向的 URL,这样在点击时就不会跳转到错误的地方。再次在底部添加以下行
jslink.href = "https://mdn.org.cn";
请注意,与 JavaScript 中的许多事情一样,有许多方法可以选择元素并将其引用存储在变量中。Document.querySelector()
是推荐的现代方法。它很方便,因为它允许你使用 CSS 选择器选择元素。上面的 querySelector()
调用将匹配文档中出现的第一个 <a>
元素。如果你想匹配并对多个元素执行操作,可以使用 Document.querySelectorAll()
,它会匹配文档中与选择器匹配的每个元素,并将它们的引用存储在一个类似 数组 的对象中,称为 NodeList
。
还有一些获取元素引用的旧方法,例如
Document.getElementById()
,它选择具有给定id
属性值的元素,例如<p id="myId">我的段落</p>
。ID 作为参数传递给函数,即const elementRef = document.getElementById('myId')
。Document.getElementsByTagName()
,它返回一个类似数组的对象,其中包含页面上给定类型的所有元素,例如<p>
s、<a>
s 等。元素类型作为参数传递给函数,即const elementRefArray = document.getElementsByTagName('p')
。
这两种方法在旧浏览器中比 querySelector()
等现代方法效果更好,但不如现代方法方便。看看你还能找到什么其他方法!
创建和放置新节点
上面只是让你尝到了可以做些什么,但让我们更进一步,看看如何创建新元素。
-
回到当前示例,让我们从获取
<section>
元素的引用开始 — 在你现有脚本的底部添加以下代码(其他行也一样)jsconst sect = document.querySelector("section");
-
现在我们使用
Document.createElement()
创建一个新段落,并像之前一样为其添加一些文本内容jsconst para = document.createElement("p"); para.textContent = "We hope you enjoyed the ride.";
-
你现在可以使用
Node.appendChild()
将新段落追加到部分的末尾jssect.appendChild(para);
-
最后,对于这部分,让我们向链接所在的段落添加一个文本节点,以使句子圆满。首先,我们将使用
Document.createTextNode()
创建文本节点jsconst text = document.createTextNode( " — the premier source for web development knowledge.", );
-
现在我们将获取链接所在的段落的引用,并将文本节点附加到它
jsconst linkPara = document.querySelector("p"); linkPara.appendChild(text);
这就是将节点添加到 DOM 所需的大部分内容 — 在构建动态界面时,你会大量使用这些方法(我们稍后会看一些示例)。
移动和删除元素
有时你可能希望移动节点,或者完全从 DOM 中删除它们。这完全是可能的。
如果我们要将包含链接的段落移动到部分的底部,我们可以这样做
sect.appendChild(linkPara);
这会将段落移动到部分的底部。你可能认为它会创建第二个副本,但事实并非如此 — linkPara
是该段落唯一副本的引用。如果你想创建副本并也添加它,你需要改用 Node.cloneNode()
。
删除节点也相当简单,至少当你拥有要删除的节点及其父节点的引用时。在当前情况下,我们只使用 Node.removeChild()
,像这样
sect.removeChild(linkPara);
当你只想根据节点自身的引用来删除节点时(这很常见),你可以使用 Element.remove()
linkPara.remove();
此方法在旧版浏览器中不受支持。它们没有方法来告诉节点删除自身,因此你必须执行以下操作
linkPara.parentNode.removeChild(linkPara);
尝试将以上代码行添加到你的代码中。
操纵样式
可以通过多种方式通过 JavaScript 操作 CSS 样式。
首先,你可以使用 Document.styleSheets
获取附加到文档的所有样式表的列表,它返回一个包含 CSSStyleSheet
对象的类数组对象。然后,你可以根据需要添加/删除样式。但是,我们不打算扩展这些功能,因为它们是一种有点过时且难以操作样式的方法。有更容易的方法。
第一种方法是直接在你要动态设置样式的元素上添加内联样式。这通过 HTMLElement.style
属性完成,该属性包含文档中每个元素的内联样式信息。你可以设置此对象的属性以直接更新元素样式。
-
例如,尝试将这些行添加到我们正在进行的示例中
jspara.style.color = "white"; para.style.backgroundColor = "black"; para.style.padding = "10px"; para.style.width = "250px"; para.style.textAlign = "center";
-
重新加载页面,你会看到样式已应用于段落。如果你在浏览器的页面检查器/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 版本使用连字符(烤串式命名法)(例如,backgroundColor
与 background-color
)。确保不要将它们混淆,否则将无法正常工作。
还有另一种常见的动态操作文档样式的方法,即将样式写入单独的样式表,并通过添加/删除类名来引用这些样式。
-
删除你之前添加到 JavaScript 中的五行代码。
-
在你的 HTML
<head>
中添加以下内容html<style> .highlight { color: white; background-color: black; padding: 10px; width: 250px; text-align: center; } </style>
-
要将此类名添加到你的元素,请使用元素的
classList
的add()
方法jspara.classList.add("highlight");
-
刷新你的页面,你会看到没有任何变化 — CSS 仍然应用于段落,但这次是通过给它一个由我们的 CSS 规则选择的类,而不是作为内联 CSS 样式。
选择哪种方法取决于你;两者都有其优点和缺点。第一种方法设置较少,适用于简单的用途,而第二种方法更纯粹(不混合 CSS 和 JavaScript,没有内联样式,这被认为是一种不良做法)。当你开始构建更大、更复杂的应用程序时,你可能会更多地使用第二种方法,但这真的取决于你。
此时,我们还没有真正做任何有用的事情!使用 JavaScript 创建静态内容毫无意义 — 你不如直接将其写入 HTML 而不使用 JavaScript。它比 HTML 更复杂,并且使用 JavaScript 创建内容还会带来其他问题(例如无法被搜索引擎读取)。
在下一节中,我们将介绍 DOM API 的更实际用途。
注意:你可以在 GitHub 上找到我们 已完成的 dom-example.html 演示(也可以在线查看)。
创建一个动态购物清单
在此练习中,我们希望你构建一个动态购物清单,允许你使用表单输入和按钮添加商品。在你在输入字段中键入商品并单击按钮或按 Enter 键后,应发生以下情况
- 商品应出现在列表中。
- 每个商品都应该有一个按钮,可以按下该按钮从列表中删除该商品。
- 每个商品旁边都应该有一个按钮,点击后将该商品从列表中删除。
- 输入字段应被清除并聚焦,为下一个商品输入做好准备。
完成的演示将如下所示 — 在你构建它之前先尝试一下!
要完成练习,请按照以下步骤操作,并确保列表的行为如上所述。
- 首先,下载我们 shopping-list.html 起始文件的副本并将其复制到某个位置。你会看到它有一些最小的 CSS、一个带标签、输入和按钮的表单、一个空列表和一个
<script>
元素。你将把所有添加都放在脚本中。 - 创建三个变量,分别引用列表 (
<ul>
)、<input>
和<button>
元素。 - 创建一个 函数,它将在点击按钮时运行。
- 在函数体内部,首先调用
preventDefault()
。由于输入字段包含在表单元素中,按下 Enter 键将触发表单提交。调用preventDefault()
将阻止表单刷新页面,从而可以向列表中添加新项目。 - 接下来,将输入的当前 值 存储在一个变量中。
- 接下来,通过将其值设置为空字符串(
""
)来清除输入元素。 - 创建三个新元素 — 一个列表项 (
<li>
)、一个<span>
和一个<button>
— 并将它们存储在变量中。 - 将 span 和 button 作为列表项的子元素追加。
- 将 span 的文本内容设置为你之前保存的输入值,并将 button 的文本内容设置为
Delete
。 - 将列表项追加到列表中。
- 将事件处理程序附加到 删除 按钮,以便在点击时,它会删除整个列表项 (
<li>...</li>
)。 - 最后,使用
focus()
方法将焦点设置到输入元素,使其准备好输入下一个购物清单项。
总结
我们已经完成了对文档和 DOM 操作的学习。至此,你应该了解 Web 浏览器在控制文档和用户 Web 体验的其他方面的重要部分。最重要的是,你应该了解文档对象模型是什么,以及如何操作它以创建有用的功能。
另见
- 你可以使用更多功能来操作你的文档。查看我们的一些参考资料,看看你能发现什么
- DOM Scripting,explainers.dev