【前端基础】2 - 3 DOM
§2-3 DOM
2-3.1 文档对象模型
文档对象模型(DOM, Document Object Model)是 Web APIs 的其中一部分,用于呈现和处理任意 HTML 或 XML 文档交互的 API。DOM 是载入到浏览器中的文档模型,以结点树的形式表现文档,每个结点表示文档中的一个组成部分(元素)。网页中的每一个元素都被视为一个个对象。
我们可以使用 DOM 操作 HTML 文档,实现网页的动态交互效果,赋予网页 “动” 的特性。
Document
接口表示任何在浏览器中所载入的网页,作为网页内容的入口,形成 DOM 树。对于每一个已载入的网页,我们可以获取表示当前 HTML 文档的 DOM 对象,即 document
。通过这个对象的 API,可以操作文档中的内容。
示例:获取 HTML 文档中的 <div>
对象。
<body>
<div>
Hello world!
</div>
</body>
const div = document.querySelector('div');
console.dir(div);
查看控制台,我们发现 div
对象含有大量属于该元素的 HTML 属性。因此,我们可以通过操作元素对象的属性从而实现操作文档。
2-3.2 获取 DOM 对象
一般地,我们使用 document
的 querySelector()
方法获取文档中指向某一元素的 DOM 对象。
const dom = document.querySelector('css selector');
方法参数是一个字符串,填写 CSS 选择器,方法会返回文档中满足该选择器的第一个元素,一个 HTMLElement
对象。
此外,还可以使用 querySelectorAll()
方法返回满足选择器的所有元素。方法会返回一个集合(不是数组),可使用循环遍历,不支持 pop()
, push()
等用于数组的方法。
const elements = document.querySelectorAll('css selector');
for (let e in elements) {
// traversal
...
}
使用 document.querySelector()
和 document.querySelectorAll()
获取 DOM 对象是推荐选择。在一些 Web 页面中,也会看到以下获取 DOM 对象的方式:
// 根据 id 获取第一个元素
document.getElementById('your-id');
// 根据给定标签获取页面中所有这一类元素
document.getElementsByName('element');
// 根据类名获取元素,获取所有指定类名的元素
document.getElementsbyClassName('you-class-name');
2-3.3 修改元素
获得了元素 DOM 对象后,可通过点表示访问对象的属性修改元素。
2-3.3.1 修改元素内容
修改元素内容有两种,一是修改元素内部文本 .innerText
,二是修改元素的 HTML 内容 .innerHTML
。
.innerText
属性接受一个字符串,会将文本内容更新到指定元素上,但不会将字符串解析为 HTML 标签。
.innerHTML
属性接受一个字符串,会将文本内容更新到指定元素上,支持 HTML 标签解析,多标签建议使用模板字符串。
<body>
<div class="info">"What am I?"</div>
</body>
const info = document.querySelector('.info');
const content = '<strong>I am a box.</strong>';
info.innerText = content; // 不支持标签解析
info.innerHTML = content; // 支持标签解析
除了修改元素内容,我们还可以修改元素的属性。
<body>
<img src="..." title="..." alt="...">
</body>
const img = document.querySelector('img');
// 修改元素的 HTML 属性
img.src = '...';
img.title = '...';
img.alt = '...';
2-3.3.2 修改元素样式
同样地,可以通过修改元素的属性,修改元素的样式,通过使用属性 style
为元素添加内联样式表实现。
<body>
<div class="box"></div>
</body>
.box {
width: 200px;
height: 200px;
background-color: pink;
}
const box = document.querySelector('.box');
// .style.xxx 修改元素的样式
box.style.width = '300px';
box.style.height = '300px';
// 多单词组成的样式使用驼峰命名
box.style.backgroundColor = 'cornflowerblue';
上述方法直接操作元素的样式属性,在实践中会显得繁琐。更常见的做法是,我们将具有固定样式的元素封装成一个类,若需要为元素添加某种样式,只需要为元素添加适当的类名即可。这在使用框架时尤为适用,即通过类名修改样式。
element.className = 'your-class-name';
此时,元素的类名将会被全部覆盖为新的值。若要反复对一个元素添加/删除类时会显得尤为不便。因此,我们可以使用下面的方法,更便捷地实现类名增删和切换。
// 为元素追加类
element.classList.add('your-class-name');
// 为元素删除类
element.classList.remove('your-class-name');
// 为元素切换类:若存在该类,则删除之;若不存在类,则追加之
element.classList.toggle('your-class-name');
2-3.3.3 自定义元素属性
绝大多数 HTML 元素都有其自带的标准属性,如 class
, id
, title
, src
等。这些属性可以通过点表示方法直接操作,如 element.title
, element.disabled
等。
除了这些标准属性之外,HTML 5 还推出了自定义属性的特性。自定义属性都有前缀 data-
,如 data-id
等。在 DOM 对象中一律使用 dataset
属性获取。
<body>
<div class="box" data-id="10">
一个示例。
</div>
</body>
const box = document.querySelector('.box');
console.log(box.dataset.id); // 10
dataset
是一个 Map
集合,存放着元素所有的自定义属性,以键值对的形式存储。
2-3.4 HTML DOM APIs
HTML DOM APIs 由一系列接口组成,这些接口提供了许多强大的功能,例如访问和控制 HTML 元素、访问和操作表单数据、在网页上拖放内容、访问浏览器导航历史记录等。
HTML DOM APIs 具有大量的接口,在有限的篇幅之内无法将这些接口都一一说明。若有需要,可在开发过程中多翻阅、查找技术文档以获取帮助。
API 赋予我们操作网页的能力,但这在实际中往往不够,我们还希望浏览器或网页能够针对特定的事件执行相应的动作。这需要使用事件监听机制。
2-3.5 DOM 树中的结点
浏览器在加载一份 HTML 文档时,会将文档中的所有元素保存在一个 DOM 树中。若想要访问/查询文档中的某一个/组结点,一般地,我们使用的是以下两种方法:
// 调用 document 对象
document.querySelector('css selector'); // 返回满足选择器的第一个元素
document.querySelectorAll('css selector'); // 返回满足选择器的所有元素(使用伪数组包装)
2-3.5.1 获取文档的根结点
文档的所有结点都以树的形式存储在 document
对象中。要获取文档的根元素 <html>
,可以:
const root = document.documentElement;
// 或
const html = document.querySelector('html');
console.log(root.tagName); // HTML
console.log(html.tagName); // HTML
一般地,我们更推荐使用 documentElement
获取文档的根元素。
2-3.5.2 根据结点的层级关系获取结点
一个 HTML 文档中可能包含多个嵌套和同级的元素。嵌套元素被称为父级元素,被嵌套元素称为子元素,而位于同级嵌套关系的元素称为兄弟元素。
以下列 HTML 文档为例:
<body>
<div class="parent">
<div class="child">
<p class="sibling">This is just a plain text.</p>
<p class="sibling">This is another sibling element.</p>
<p class="sibling">This is just another plain text.</p>
</div>
</div>
</body>
我们首先通过元素查询获得 .child
元素,明明变量为 child
。由 DOM 树的特点,我们可以通过树形关系获取该元素的父级元素、子级元素和兄弟元素。
// 获取父级元素
const parent = child.parentNode;
console.log(parent.tagName, parent.classList[0]); // DIV parent
// 获取所有子结点(包含注释,使用伪数组包装)
const childNodes = child.childNodes;
for (let i = 0; i < childNodes.length; i++) {
console.log(childNodes[i]); // #text
}
// 获取子元素(不含注释,使用伪数组包装)
const children = child.children;
for (let i = 0; i < children.length; i++) {
console.log(children[i].tagName, children[i].classList[0]); // P siblings
}
而查询兄弟元素,可使用属性 previousElementSibling
或 nextElementSibling
分别查询该元素的前一个或后一个兄弟元素(不含注释)。
// 获取兄弟元素
const mid = children[1];
const elder = mid.previousElementSibling;
const younger = mid.nextElementSibling;
console.log(elder.tagName, elder.classList[0], elder.innerHTML);
console.log(younger.tagName, younger.classList[0], younger.innerHTML);
若要获取包含注释在内的兄弟结点,则使用属性 previousSibling
或 nextSibling
即可。
2-3.5.3 添加结点
我们会在一些网页中看到,用户往下滚动网页时,我们首先从数据库中获取最新的内容,然后再将这些获取得到的内容实时显示在网页中。这就需要使用 JavaScript 动态地在页面中添加结点。
以上文的 HTML 文档为例:
// 添加结点的一种办法:追加
// 1. 首先创建元素
const newElement = document.createElement('div');
newElement.classList.add('parent');
// 2. 然后将其追加到某一父级元素中,充当其子元素
document.body.appendChild(div);
此时,新的元素就作为 <body>
的最后一个子元素追加进来。使用 appendChild
方法添加的元素都会添加为该元素的最后一个子元素。最后得到如下结构:
<body>
<div class="parent">
<div class="child">
<p class="sibling">...</p>
<p class="sibling">...</p>
<p class="sibling">...</p>
</div>
</div>
<script>...</script>
<div class="parent"></div>
</body>
这种方法显然有着明显的局限:每一次都只能添加为末尾的子元素。若要指定添加的位置,则应当使用 insertBefore()
方法。
// 使用 insertBefore() 指定添加位置
// 用法:insertBefore(newElement, insertPosition)
document.body.insertBefore(newElement, document.body.children[0]);
这样,newElement
就会被添加到 <body>
的第一个子元素前,作为 <body>
新的首个子元素。
2-3.5.4 克隆结点与删除结点
除了查找结点、添加结点之外,我们还常用到克隆结点。克隆结点的其中一个用途在于无限滚动动画。
以下列 HTML 文档为例,我们希望实现无限滚动动画(CSS 忽略)。
<div class="slider">
<ul class="slider-container">
<li class="slider-item" data-course="HTML">HTML</li>
<li class="slider-item" data-course="CSS">CSS</li>
<li class="slider-item" data-course="JavaScript">JavaScript</li>
<li class="slider-item" data-course="PHP">PHP</li>
<li class="slider-item" data-course="Java">Java</li>
<li class="slider-item" data-course="C/C++">C/C++</li>
<li class="slider-item" data-course="Python">Python</li>
</ul>
</div>
若要实现无限滚动,则需要让动画的最后一帧与第一帧重叠。在过去,我们需要手动复制重叠帧的元素,但这一步可以使用克隆结点实现。
const container = document.querySelector('.slider-container');
const items = document.querySelector('.slider-container').children;
for (let i = 0; i < 6; i++) {
// 使用 cloneNode() 方法克隆结点,默认为浅克隆
// 为了能够克隆结点中的内容,传入参数 true 变为深克隆
container.appendChild(items[i].cloneNode(true));
}
另外,我们也可以删除结点。例如,若我们不需要显示 PHP
的结点时,我们需要通过其父元素将其删除。
const python = document.querySelector('.slider-item[data-course=Python]');
container.removeChild(python);
该方法会返回被删除元素的引用。
2-3.X 外部链接
Document - Web APIs | MDN (mozilla.org)