Document 文档对象模型
Zhongjun Qiu 元婴开发者

全面梳理 DOM 与 BOM 的核心概念与用法,包括节点的访问与遍历、元素的搜索与筛选、文档结构的动态创建与修改、样式操作、窗口尺寸与滚动控制等内容,帮助前端开发者打牢文档结构控制的基础能力。

image

文档对象模型(DOM)

文档对象模型(Document Object Model),简称 DOM,将所有页面内容表示为可以修改的对象。

document 对象是页面的主要“入口点”。我们可以使用它来更改或创建页面上的任何内容。

1
2
3
4
5
// 将背景颜色修改为红色
document.body.style.background = "red";

// 在 1 秒后将其修改回来
setTimeout(() => document.body.style.background = "", 1000);

浏览器对象模型(BOM)

浏览器对象模型(Browser Object Model),简称 BOM,表示由浏览器(主机环境)提供的用于处理文档(document)之外的所有内容的其他对象。

  • navigator 对象提供了有关浏览器和操作系统的背景信息。navigator 有许多属性,但是最广为人知的两个属性是:navigator.userAgent —— 关于当前浏览器,navigator.platform —— 关于平台(有助于区分 Windows/Linux/Mac 等)。
  • location 对象允许我们读取当前 URL,并且可以将浏览器重定向到新的 URL。

这是我们可以如何使用 location 对象的方法:

1
2
3
4
alert(location.href); // 显示当前 URL
if (confirm("Go to Wikipedia?")) {
location.href = "https://wikipedia.org"; // 将浏览器重定向到另一个 URL
}

函数 alert/confirm/prompt 也是 BOM 的一部分:它们与文档(document)没有直接关系,但它代表了与用户通信的纯浏览器方法。

DOM 遍历

image

纯元素导航

上面列出的导航(navigation)属性引用 所有 节点。例如,在 childNodes 中我们可以看到文本节点,元素节点,甚至包括注释节点(如果它们存在的话)。

但是对于很多任务来说,我们并不想要文本节点或注释节点。我们希望操纵的是代表标签的和形成页面结构的元素节点。

所以,让我们看看更多只考虑 元素节点 的导航链接(navigation link):

image

这些链接和我们在上面提到过的类似,只是在词中间加了 Element

  • children —— 仅那些作为元素节点的子代的节点。
  • firstElementChildlastElementChild —— 第一个和最后一个子元素。
  • previousElementSiblingnextElementSibling —— 兄弟元素。
  • parentElement —— 父元素。

指定元素搜索

document.getElementById

1
2
3
4
5
6
7
8
9
10
11
<div id="elem">
<div id="elem-content">Element</div>
</div>

<script>
// 获取该元素
let elem = document.getElementById('elem');

// 将该元素背景改为红色
elem.style.background = 'red';
</script>

querySelectorAll

到目前为止,最通用的方法是 elem.querySelectorAll(css),它返回 elem 中与给定 CSS 选择器匹配的所有元素。

在这里,我们查找所有为最后一个子元素的 <li> 元素:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<ul>
<li>The</li>
<li>test</li>
</ul>
<ul>
<li>has</li>
<li>passed</li>
</ul>
<script>
let elements = document.querySelectorAll('ul > li:last-child');

for (let elem of elements) {
alert(elem.innerHTML); // "test", "passed"
}
</script>

querySelector

elem.querySelector(css) 调用会返回给定 CSS 选择器的第一个元素。

matches

1
2
3
4
5
6
7
8
9
10
11
<a href="http://example.com/file.zip">...</a>
<a href="http://ya.ru">...</a>

<script>
// 不一定是 document.body.children,还可以是任何集合
for (let elem of document.body.children) {
if (elem.matches('a[href$="zip"]')) {
alert("The archive reference: " + elem.href );
}
}
</script>

closest

元素的祖先(ancestor)是:父级,父级的父级,它的父级等。祖先们一起组成了从元素到顶端的父级链。

elem.closest(css) 方法会查找与 CSS 选择器匹配的最近的祖先。elem 自己也会被搜索。

换句话说,方法 closest 在元素中得到了提升,并检查每个父级。如果它与选择器匹配,则停止搜索并返回该祖先。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<h1>Contents</h1>

<div class="contents">
<ul class="book">
<li class="chapter">Chapter 1</li>
<li class="chapter">Chapter 2</li>
</ul>
</div>

<script>
let chapter = document.querySelector('.chapter'); // LI

alert(chapter.closest('.book')); // UL
alert(chapter.closest('.contents')); // DIV

alert(chapter.closest('h1')); // null(因为 h1 不是祖先)
</script>

实时的集合

所有的 "getElementsBy*" 方法都会返回一个 实时的(live) 集合。这样的集合始终反映的是文档的当前状态,并且在文档发生更改时会“自动更新”。

1
2
3
4
5
6
7
8
9
10
11
12
<div>First div</div>

<script>
let divs = document.getElementsByTagName('div');
alert(divs.length); // 1
</script>

<div>Second div</div>

<script>
alert(divs.length); // 2
</script>

相反,querySelectorAll 返回的是一个 静态的 集合。就像元素的固定数组。

1
2
3
4
5
6
7
8
9
10
11
12
<div>First div</div>

<script>
let divs = document.querySelectorAll('div');
alert(divs.length); // 1
</script>

<div>Second div</div>

<script>
alert(divs.length); // 1
</script>

现在我们可以很容易地看到不同之处。在文档中出现新的 div 后,静态集合并没有增加。

节点属性

不同的 DOM 节点可能有不同的属性。例如,标签 <a> 相对应的元素节点具有链接相关的(link-related)属性,标签 <input> 相对应的元素节点具有与输入相关的属性,等。文本节点与元素节点不同。但是所有这些标签对应的 DOM 节点之间也存在共有的属性和方法,因为所有类型的 DOM 节点都形成了一个单一层次的结构(single hierarchy)。

每个 DOM 节点都属于相应的内建类。

image

修改Document

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>

<script>
let div = document.createElement('div');
div.className = "alert";
div.innerHTML = "<strong>Hi there!</strong> You've read an important message.";

document.body.append(div);
</script>

样式和类

  • className —— 字符串值,可以很好地管理整个类的集合。
  • classList —— 具有 add/remove/toggle/contains 方法的对象,可以很好地支持单个类。

要改变样式:

  • style 属性是具有驼峰(camelCased)样式的对象。对其进行读取和修改与修改 "style" 特性(attribute)中的各个属性具有相同的效果。要了解如何应用 important 和其他特殊内容 —— 在 MDN 中有一个方法列表。
  • style.cssText 属性对应于整个 "style" 特性(attribute),即完整的样式字符串。

要读取已解析的(resolved)样式(对于所有类,在应用所有 CSS 并计算最终值之后):

  • getComputedStyle(elem, [pseudo]) 返回与 style 对象类似的,且包含了所有类的对象。只读。

Window 大小和滚动

几何:

  • 文档可见部分的 width/height(内容区域的 width/height):document.documentElement.clientWidth/clientHeight

  • 整个文档的 width/height,其中包括滚动出去的部分:

    1
    2
    3
    4
    5
    let scrollHeight = Math.max(
    document.body.scrollHeight, document.documentElement.scrollHeight,
    document.body.offsetHeight, document.documentElement.offsetHeight,
    document.body.clientHeight, document.documentElement.clientHeight
    );

滚动:

  • 读取当前的滚动:window.pageYOffset/pageXOffset
  • 更改当前的滚动:
    • window.scrollTo(pageX,pageY) —— 绝对坐标,
    • window.scrollBy(x,y) —— 相对当前位置进行滚动,
    • elem.scrollIntoView(top) —— 滚动以使 elem 可见(elem 与窗口的顶部/底部对齐)
 REWARD AUTHOR
 Comments
Comment plugin failed to load
Loading comment plugin