【前端基础】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 对象

一般地,我们使用 documentquerySelector() 方法获取文档中指向某一元素的 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
}

而查询兄弟元素,可使用属性 previousElementSiblingnextElementSibling 分别查询该元素的前一个或后一个兄弟元素(不含注释)。

// 获取兄弟元素
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);

若要获取包含注释在内的兄弟结点,则使用属性 previousSiblingnextSibling 即可。

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 外部链接

DOM (Document Object Model) - MDN Web Docs Glossary: Definitions of Web-related terms | MDN (mozilla.org)

Document - Web APIs | MDN (mozilla.org)

Window: document property - Web APIs | MDN (mozilla.org)

The HTML DOM API - Web APIs | MDN (mozilla.org)

posted @ 2024-03-09 21:18  Zebt  阅读(1)  评论(0编辑  收藏  举报