前言
JavaScript最独特且有用的能力之一是它能够操作DOM。但是DOM是什么,我们如何改变它呢?让我们直接进入主题…
文章概述
本文主要讲述的内容。
- 解释什么是与网页相关的 DOM。
- 解释 "节点 "和 "元素 "的区别。
- 解释如何使用 "选择器 "定位节点。
- 解释查找/添加/删除和更改 DOM 节点的基本方法。
- 解释 "节点列表 "和 "节点数组 "的区别。
- 解释什么是 "冒泡 "及其工作原理。
- DOM - 文档对象模型
- DOM (或文档对象模型)是网页内容的树状表示法 - 一个由 "节点 "组成的树,根据它们在 HTML 文档中的排列方式而具有不同的关系。
<div id="container">
<div class="display"></div>
<div class="controls"></div>
</div>
在上面的示例中,是的“子项”,是 的同级。把它想象成一个家谱。 是一个父级,其子级在下一个级别,每个级都有自己的“分支”。
使用选择器定位节点
在使用DOM时,您可以使用“atten”来定位您想要使用的节点。您可以使用CSS样式选择器和关系属性的组合来定位您想要的节点。让我们从CSS样式选择器开始。在上面的示例中,您可以使用以下选择器来引用:<div class="display"></div>
div.display
.display
#container > .display
div#container > div.display
您也可以使用关系选择器(例如.firstChild
或.lastChild
)与节点所拥有的特殊属性一起使用。
const container = document.querySelector('#container');
// selects the #container div (don't worry about the syntax, we'll get there)
console.dir(container.firstElementChild);
// selects the first child of #container => .display
const controls = document.querySelector('.controls');
// selects the .controls div
console.dir(controls.previousElementSibling);
// selects the prior sibling => .display
因此,你要根据某个节点与周围节点的关系来识别它。
DOM 方法
网页浏览器解析 HTML 代码时,会将其转换为上文提到的 DOM。其中一个主要区别是,这些节点都是对象,并附加了许多属性和方法。这些属性和方法是我们使用 JavaScript 操作网页的主要工具。我们将从查询选择器开始–这些选择器可以帮助你定位节点。
查询选择器
- element.querySelector(selector) - 返回选择器第一个匹配项的引用。
- element.querySelectorAll(selectors) - 返回一个 “节点列表”,其中包含对所有匹配选择器的引用。
还有其他一些更具体的查询,可以提供潜在的(微不足道的)性能优势,但我们现在不打算讨论它们。
需要注意的是,使用 querySelectorAll 时,返回值不是数组。它看起来像一个数组,操作起来也有点像数组,但它实际上是一个 “节点列表”。最大的区别在于节点列表中缺少几个数组方法。如果出现问题,一种解决办法是将节点列表转换为数组。您可以使用或散布运算符 Array.from() 来实现这一目的。
创建元素
- document.createElement(tagName, [options]) - 创建一个标签类型为 tagName 的新元素。在这种情况下,您可以在函数中添加一些可选参数。此时不必担心这些参数。
const div = document.createElement('div');
该函数不会将新元素放入 DOM,而只是在内存中创建。这样,您就可以在将元素放入页面之前对其进行操作(添加样式、类、ID、文本等)。您可以使用以下方法之一将元素放入 DOM。
追加元素
- parentNode.appendChild(childNode) - 将 childNode 追加为 parentNode 的最后一个子节点。
- parentNode.insertBefore(newNode, referenceNode) - 在 referenceNode 之前将 newNode 插入 parentNode。
删除元素
- parentNode.removeChild(child) - 在 DOM 上将子节点从 parentNode 中移除,并返回子节点的引用。
更改元素
有了对元素的引用后,就可以使用该引用来更改元素自身的属性。这样就可以进行许多有用的更改,如添加/删除和更改属性、更改类、添加内联样式信息等。
const div = document.createElement('div');
// creates a new div referenced in the variable 'div'
添加内联样式
div.style.color = 'blue';
// adds the indicated style rule
div.style.cssText = 'color: blue; background: white;';
// adds several style rules
div.setAttribute('style', 'color: blue; background: white;');
// adds several style rules
请注意,如果您要从 JS 访问带 kebab 大小写的 CSS 规则,要么需要使用 camelCase,要么需要使用括号符号代替破折号符号。
div.style.background-color // doesn't work - attempts to subtract color from div.style.background
div.style.backgroundColor // accesses the div's background-color style
div.style['background-color'] // also works
div.style.cssText = "background-color: white;" // sets the div's background-color by assigning a CSS string
编辑属性
div.setAttribute('id', 'theDiv');
// if id exists, update it to 'theDiv', else create an id
// with value "theDiv"
div.getAttribute('id');
// returns value of specified attribute, in this case
// "theDiv"
div.removeAttribute('id');
// removes specified attribute
使用类
div.classList.add('new');
// adds class "new" to your new div
div.classList.remove('new');
// removes "new" class from div
div.classList.toggle('active');
// if div doesn't have class "active" then add it, or if
// it does, then remove it
通常情况下,切换 CSS 样式比添加和删除内联 CSS 更标准(也更简洁)。
添加文本内容
div.textContent = 'Hello World!'
// creates a text node containing "Hello World!" and
// inserts it in div
添加 HTML 内容
div.innerHTML = '<span>Hello World!</span>';
// renders the HTML inside div
请注意,textContent 更适合用于添加文本,而 innerHTML 则应慎用,因为如果滥用,可能会带来安全风险。如果你想了解使用方法,请观看这段视频。
让我们花一点时间回顾一下我们已经讲过的内容,并在继续之前给你一个练习这些内容的机会。请观看创建 DOM 元素并将其添加到网页的示例。
<!-- your HTML file: -->
<body>
<h1>
THE TITLE OF YOUR WEBPAGE
</h1>
<div id="container"></div>
</body>
// your JavaScript file
const container = document.querySelector('#container');
const content = document.createElement('div');
content.classList.add('content');
content.textContent = 'This is the glorious text-content!';
container.appendChild(content);
在 JavaScript 文件中,我们首先要获取 HTML 中已存在的 div 的引用。然后,我们创建一个新的 div 并将其存储在变量 . 我们为 div 添加一个类和一些文本,最后将该 div 附加到 .NET 文件中。总之,这个过程非常简单。运行 JavaScript 代码后,我们的 DOM 树将如下所示:
<!-- The DOM -->
<body>
<h1>
THE TITLE OF YOUR WEBPAGE
</h1>
<div id="container">
<div class="content">
This is the glorious text-content!
</div>
</div>
</body>
请记住,JavaScript 不会改变 HTML,但会改变 DOM–HTML 文件的外观不会改变,但 JavaScript 会改变浏览器的渲染效果。
在大多数情况下,只要运行 JS 文件或在 HTML 中遇到脚本标记,就会运行 JavaScript。如果将 JavaScript 放在文件的顶部,许多 DOM 操作方法就会失效,因为 JS 代码是在 DOM 中创建节点之前运行的。解决这个问题的最简单方法是将 JavaScript 放在 HTML 文件的底部,这样它就能在 DOM 节点解析和创建后运行。
或者,也可以在 HTML 文档中链接 JavaScript 文件。使用包含 JS 文件路径属性的标签,并在 HTML 解析后包含加载文件的关键字,如:
head>
<script src="js-file.js" defer></script>
</head>
练习
将上面的示例复制到自己电脑上的文件中。为使其正常工作,您需要提供 HTML 骨架的其余部分,并链接您的 JavaScript 文件,或将 JavaScript 放入页面上的脚本标记中。确保一切正常后再继续!
使用 ONLY JavaScript 和上述 DOM 方法将以下元素添加到容器中。
- 带有红色文字,上面写着 "Hey I’m red!”
<p>
。 - 带有蓝色文字,上面写着 “I’m a blue h3!”
<h3>
。 - 带有黑色边框和粉红色背景的表格,其中包含以下元素:
<div>
- 另一个写着“I’m in a div”
<h1>
- 一个写着“ME TOO!”
<p>
- 提示:使用 createElement 创建后,将 和 附加到它,然后再将其添加到容器中。
- 另一个写着“I’m in a div”
事件
现在,我们已经掌握了使用 JavaScript 操作 DOM 的方法,下一步就是学习如何动态地或按需实现这一操作!事件就是在网页上创造奇迹的方法。事件是网页上发生的操作,如鼠标点击或按键,使用 JavaScript 我们可以让网页监听这些事件并做出反应。
主要有三种方法: 可以直接在 HTML 元素上指定功能属性,也可以在 JavaScript 的 DOM 节点上设置表单属性(, ,etc),还可以在 JavaScript 的 DOM 节点上附加事件监听器。事件监听器无疑是首选方法,但你也会经常看到其他方法的使用,因此我们将介绍这三种方法。
我们将创建 3 个按钮,点击后都会提示 “Hello World”。请使用自己的 HTML 文件,或使用 CodePen 等软件尝试一下。
方法 1
<button onclick="alert('Hello World')">Click Me</button>
这种解决方案并不理想,因为我们会在 HTML 中加入大量 JavaScript。此外,我们只能为每个 DOM 元素设置一个 "onclick "属性,因此无法使用此方法运行多个独立函数来响应点击事件。
方法2
<!-- the HTML file -->
<button id="btn">Click Me</button>
// the JavaScript file
const btn = document.querySelector('#btn');
btn.onclick = () => alert("Hello World");
这样就好多了。我们将 JS 从 HTML 移到了一个 JS 文件中,但仍然存在一个 DOM 元素只能有一个 "onclick "属性的问题。
方法 3
<!-- the HTML file -->
<button id="btn">Click Me Too</button>
// the JavaScript file
const btn = document.querySelector('#btn');
btn.addEventListener('click', () => {
alert("Hello World");
});
现在,我们保持了关注点的分离,而且还允许在需要时使用多个事件监听器。方法 3 更灵活、更强大,不过设置起来要复杂一些。
请注意,上述 3 种方法都可以与命名函数一起使用,例如:
<!-- the HTML file -->
<!-- METHOD 1 -->
<button onclick="alertFunction()">CLICK ME BABY</button>
// the JavaScript file
function alertFunction() {
alert("YAY! YOU DID IT!");
}
// METHOD 2
btn.onclick = alertFunction;
// METHOD 3
btn.addEventListener('click', alertFunction);
使用命名函数可以大大简化代码,如果函数需要在多个地方使用,使用命名函数是一个非常好的主意。
通过这三种方法,我们可以向调用的函数传递一个参数,从而获取有关事件的更多信息。在你自己的机器上试试吧:
btn.addEventListener('click', function (e) {
console.log(e);
});
请注意,这是 addEventListener 的回调。
该函数中是一个引用事件本身的对象。在该对象中,您可以访问许多有用的属性和方法(存在于对象内部的函数),例如按下了哪个鼠标按钮或按键,或事件目标(被点击的 DOM 节点)的相关信息。
试试这个:
btn.addEventListener('click', function (e) {
console.log(e.target);
});
现在又是这样:
btn.addEventListener('click', function (e) {
e.target.style.background = 'blue';
});
为节点组附加监听器
如果要将大量类似的事件监听器附加到许多元素上,这看起来可能需要很多代码。有几种方法可以更有效地做到这一点。我们在上文已经了解到,我们可以通过查询选择器列表(querySelectorAll(‘selector’))获得与特定选择器匹配的所有项目的节点列表。 为了给每个项目添加监听器,我们只需遍历整个列表,如下所示:
<div id="container">
<button id="1">Click Me</button>
<button id="2">Click Me</button>
<button id="3">Click Me</button>
</div>
// buttons is a node list. It looks and acts much like an array.
const buttons = document.querySelectorAll('button');
// we use the .forEach method to iterate through each button
buttons.forEach((button) => {
// and for each one we add a 'click' listener
button.addEventListener('click', () => {
alert(button.id);
});
});
这只是 DOM 操作和事件处理的冰山一角,但足以让你开始进行一些练习。在目前的示例中,我们只使用了 "click "事件,但还有更多事件可供使用。
一些有用的事件包括
- click
- dblclick
- keydown
- keyup