DOM 树

HTML 文档的主干是标签(tag)。

根据文档对象模型(DOM),每个 HTML 标签都是一个对象。嵌套的标签是闭合标签的“子标签(children)”。标签内的文本也是一个对象。

所有这些对象都可以通过 JavaScript 来访问,我们可以使用它们来修改页面。

例如,document.body 是表示 <body> 标签的对象。

运行这段代码会使 <body> 保持 3 秒红色状态:

document.body.style.background = 'red'; // 将背景设置为红色setTimeout(() => document.body.style.background = '', 3000); // 恢复回去

在这,我们使用了 style.background 来修改 document.body 的背景颜色,但是还有很多其他的属性,例如:

  • innerHTML — 节点的 HTML 内容。
  • offsetWidth — 节点宽度(以像素度量)
  • ……等。

很快,我们将学习更多操作 DOM 的方法,但首先我们需要了解 DOM 的结构。

DOM 的例子

让我们从下面这个简单的文档(document)开始:

<!DOCTYPE HTML>
<html>
<head><title>About elk</title>
</head>
<body>The truth about elk.
</body>
</html>

DOM 将 HTML 表示为标签的树形结构。它看起来如下所示:

在上面的图片中,你可以点击元素(element)节点,它们的子节点会打开/折叠。

每个树的节点都是一个对象。

标签被称为 元素节点(或者仅仅是元素),并形成了树状结构:<html> 在根节点,<head><body> 是其子项,等。

元素内的文本形成 文本节点,被标记为 #text。一个文本节点只包含一个字符串。它没有子项,并且总是树的叶子。

例如,<title> 标签里面有文本 "About elk"

请注意文本节点中的特殊字符:

  • 换行符:(在 JavaScript 中为 \n
  • 空格:

空格和换行符都是完全有效的字符,就像字母和数字。它们形成文本节点并成为 DOM 的一部分。所以,例如,在上面的示例中,<head> 标签中的 <title> 标签前面包含了一些空格,并且该文本变成了一个 #text 节点(它只包含一个换行符和一些空格)。

只有两个顶级排除项:

  1. 由于历史原因,<head> 之前的空格和换行符均被忽略。
  2. 如果我们在 </body> 之后放置一些东西,那么它会被自动移动到 body 内,并处于 body 中的最下方,因为 HTML 规范要求所有内容必须位于 <body> 内。所以 </body> 之后不能有空格。

在其他情况下,一切都很简单 — 如果文档中有空格(就像任何字符一样),那么它们将成为 DOM 中的文本节点,而如果我们删除它们,则不会有任何空格。

这是没有空格的文本节点:

<!DOCTYPE HTML>
<html><head><title>About elk</title></head><body>The truth about elk.</body></html>

字符串开头/结尾处的空格,以及只有空格的文本节点,通常会被工具隐藏

与 DOM 一起使用的浏览器工具(即将介绍)通常不会在文本的开始/结尾显示空格,并且在标签之间也不会显示空文本节点(换行符)。

开发者工具通过这种方式节省屏幕空间。

在本教程中,如果这些空格和空文本节点无关紧要时,我们在后面出现的关于 DOM 的示意图中会忽略它们。这样的空格通常不会影响文档的显示方式。

自动修正

如果浏览器遇到格式不正确的 HTML,它会在形成 DOM 时自动更正它。

例如,顶级标签总是 <html>。即使它不存在于文档中 — 它也会出现在 DOM 中,因为浏览器会创建它。对于 <body> 也是一样。

例如,如果一个 HTML 文件中只有一个单词 “Hello”,浏览器则会把它包装到 <html><body> 中,并且会添加所需的 <head>,DOM 将会变成下面这样:

在生成 DOM 时,浏览器会自动处理文档中的错误,关闭标签等。

一个没有关闭标签的文档:

<p>Hello
<li>Mom
<li>and
<li>Dad

……将成为一个正常的 DOM,因为浏览器在读取标签时会填补缺失的部分:

表格永远有 <tbody>

表格是一个有趣的“特殊的例子”。按照 DOM 规范,它们必须具有 <tbody> 标签,但 HTML 文本可能会忽略它。然后浏览器在创建 DOM 时,自动地创建了 <tbody>

对于 HTML:

<table id="table"><tr><td>1</td></tr></table>

DOM 结构会变成:

看到了吗?<tbody> 出现了。我们应该记住这一点,以免在使用表格时,对这种情况感到惊讶。

其他节点类型

除了元素和文本节点外,还有一些其他的节点类型。

例如,注释:

<!DOCTYPE HTML>
<html>
<body>The truth about elk.<ol><li>An elk is a smart</li><!-- comment --><li>...and cunning animal!</li></ol>
</body>
</html>

在这里我们可以看到一个新的树节点类型 — comment node,被标记为 #comment,它在两个文本节点之间。

我们可能会想 — 为什么要将注释添加到 DOM 中?它不会对视觉展现产生任何影响吗。但是有一条规则 — 如果一些内容存在于 HTML 中,那么它也必须在 DOM 树中。

HTML 中的所有内容,甚至注释,都会成为 DOM 的一部分。

甚至 HTML 开头的 <!DOCTYPE...> 指令也是一个 DOM 节点。它在 DOM 树中位于 <html> 之前。很少有人知道这一点。我们不会触及那个节点,我们甚至不会在图表中绘制它,但它确实就在那里。

表示整个文档的 document 对象,在形式上也是一个 DOM 节点。

一共有 12 种节点类型。实际上,我们通常用到的是其中的 4 种:

  1. document — DOM 的“入口点”。
  2. 元素节点 — HTML 标签,树构建块。
  3. 文本节点 — 包含文本。
  4. 注释 — 有时我们可以将一些信息放入其中,它不会显示,但 JS 可以从 DOM 中读取它。

总结

HTML/XML 文档在浏览器内均被表示为 DOM 树。

  • 标签(tag)成为元素节点,并形成文档结构。
  • 文本(text)成为文本节点。
  • ……等,HTML 中的所有东西在 DOM 中都有它的位置,甚至对注释也是如此。

我们可以使用开发者工具来检查(inspect)DOM 并手动修改它。

在这里,我们介绍了基础知识,入门最常用和最重要的行为。在 https://developers.google.cn/web/tools/chrome-devtools 上有关于 Chrome 开发者工具的详细文档说明。学习这些工具的最佳方式就是到处点一点看一看,阅读菜单:大多数选项都很明显。而后,当你大致了解它们后,请阅读文档并学习其余内容。

遍历 DOM

DOM 让我们可以对元素和它们中的内容做任何事,但是首先我们需要获取到对应的 DOM 对象。

对 DOM 的所有操作都是以 document 对象开始。它是 DOM 的主“入口点”。从它我们可以访问任何节点。

这里是一张描述对象间链接的图片,通过这些链接我们可以在 DOM 节点之间移动。

让我们更详细地讨论它们吧。

在最顶层:documentElement 和 body

最顶层的树节点可以直接作为 document 的属性来使用:

  • <html> = document.documentElement

    最顶层的 document 节点是 document.documentElement。这是对应 <html> 标签的 DOM 节点。

  • <body> = document.body

    另一个被广泛使用的 DOM 节点是 <body> 元素 — document.body

  • <head> = document.head

    <head> 标签可以通过 document.head 访问。

这里有个问题:document.body 的值可能是 null

脚本无法访问在运行时不存在的元素。

尤其是,如果一个脚本是在 <head> 中,那么脚本是访问不到 document.body 元素的,因为浏览器还没有读到它。

所以,下面例子中的第一个 alert 显示 null

<html><head><script>alert( "From HEAD: " + document.body ); // null,这里目前还没有 <body></script>
</head><body><script>alert( "From BODY: " + document.body ); // HTMLBodyElement,现在存在了</script></body>
</html>

在 DOM 的世界中,null 就意味着“不存在”

在 DOM 中,null 值就意味着“不存在”或者“没有这个节点”。

子节点:childNodes,firstChild,lastChild

从现在开始,我们将使用下面这两个术语:

  • 子节点(或者叫作子) — 对应的是直系的子元素。换句话说,它们被完全嵌套在给定的元素中。例如,<head><body> 就是 <html> 元素的子元素。
  • 子孙元素 — 嵌套在给定元素中的所有元素,包括子元素,以及子元素的子元素等。

例如,这里 <body> 有子元素 <div><ul>(以及一些空白的文本节点):

<html>
<body><div>Begin</div><ul><li><b>Information</b></li></ul>
</body>
</html>

……<body> 元素的子孙元素不仅包含直接的子元素 <div><ul>,还包含像 <li><ul> 的子元素)和 <b><li> 的子元素)这样的元素 — 整个子树。

childNodes 集合列出了所有子节点,包括文本节点。

下面这个例子显示了 document.body 的子元素:

<html>
<body><div>Begin</div><ul><li>Information</li></ul><div>End</div><script>for (let i = 0; i < document.body.childNodes.length; i++) {alert( document.body.childNodes[i] ); // Text, DIV, Text, UL, ..., SCRIPT}</script>...more stuff...
</body>
</html>

请注意这里的一个有趣的细节。如果我们运行上面这个例子,所显示的最后一个元素是 <script>。实际上,文档下面还有很多东西,但是在这个脚本运行的时候,浏览器还没有读到下面的内容,所以这个脚本也就看不到它们。

firstChildlastChild 属性是访问第一个和最后一个子元素的快捷方式。

它们只是简写。如果元素存在子节点,那么下面的脚本运行结果将是 true:

elem.childNodes[0] === elem.firstChild
elem.childNodes[elem.childNodes.length - 1] === elem.lastChild

这里还有一个特别的函数 elem.hasChildNodes() 用于检查节点是否有子节点。

遍历 DOM 集合

正如我们看到的那样,childNodes 看起来就像一个数组。但实际上它并不是一个数组,而是一个 集合 — 一个类数组的可迭代对象。

这个性质会导致两个重要的结果:

  1. 我们可以使用 for..of 来迭代它:
for (let node of document.body.childNodes) {alert(node); // 显示集合中的所有节点
}

这是因为集合是可迭代的(提供了所需要的 Symbol.iterator 属性)。

  1. 无法使用数组的方法,因为它不是一个数组:
alert(document.body.childNodes.filter); // undefined(这里没有 filter 方法!)

集合的性质所得到的第一个结果很不错。第二个结果也还可以忍受,因为如果我们想要使用数组的方法的话,我们可以使用 Array.from 方法来从集合创建一个“真”数组:

alert( Array.from(document.body.childNodes).filter ); // function

DOM 集合是只读的

DOM 集合,甚至可以说本章中列出的 所有 导航(navigation)属性都是只读的。

我们不能通过类似 childNodes[i] = ... 的操作来替换一个子节点。

修改子节点需要使用其它方法。我们将会在下一章中看到它们。

DOM 集合是实时的

除小部分例外,几乎所有的 DOM 集合都是 实时 的。换句话说,它们反映了 DOM 的当前状态。

如果我们保留一个对 elem.childNodes 的引用,然后向 DOM 中添加/移除节点,那么这些节点的更新会自动出现在集合中。

不要使用 for..in 来遍历集合

可以使用 for..of 对集合进行迭代。但有时候人们会尝试使用 for..in 来迭代集合。

请不要这么做。for..in 循环遍历的是所有可枚举的(enumerable)属性。集合还有一些“额外的”很少被用到的属性,通常这些属性也是我们不期望得到的:

<body>
<script>// 显示 0,1,length,item,values 及其他。for (let prop in document.body.childNodes) alert(prop);
</script>
</body>

兄弟节点和父节点

兄弟节点(Sibling) 是指有同一个父节点的节点。

例如,<head><body> 就是兄弟节点:

<html><head>...</head><body>...</body>
</html>
  • <body> 可以说是 <head> 的“下一个”或者“右边”兄弟节点。
  • <head> 可以说是 <body> 的“前一个”或者“左边”兄弟节点。

下一个兄弟节点在 nextSibling 属性中,上一个是在 previousSibling 属性中。

可以通过 parentNode 来访问父节点。

例如:

// <body> 的父节点是 <html>
alert( document.body.parentNode === document.documentElement ); // true// <head> 的后一个是 <body>
alert( document.head.nextSibling ); // HTMLBodyElement// <body> 的前一个是 <head>
alert( document.body.previousSibling ); // HTMLHeadElement

总结

给定一个 DOM 节点,我们可以使用导航(navigation)属性访问其直接的邻居。

这些属性主要分为两组:

  • 对于所有节点:parentNodechildNodesfirstChildlastChildpreviousSiblingnextSibling
  • 仅对于元素节点:parentElementchildrenfirstElementChildlastElementChildpreviousElementSiblingnextElementSibling

某些类型的 DOM 元素,例如 table,提供了用于访问其内容的其他属性和集合。

DOM深入学习 --- DOM 树介绍,如何遍历 DOM 树节点(一)相关推荐

  1. Html遍历dom树,jQuery向上遍历DOM树之parents(),parent(),closest()之间的区别

    在这个sprint中,因为要写前端UI,所以用到了jQuery,但是jQuery在向上遍历DOM树的API中,有parents(). parent().closest()这几个,一直不太清楚它们具体的 ...

  2. DOM系列:DOM树和遍历DOM

    上一节,咱们整理了DOM系列中的第一篇,主要介绍浏览器与DOM相关的知识.从标题中我们可以看出来,今天所要学的东西包含两个部分,第一部分是DOM树,第二部分是遍历DOM.如果你和我一样对于DOM树和遍 ...

  3. 前端基础学习——JavaScript之BOM模型与DOM模型

    前面还有JavaScript基础介绍,有兴趣的朋友可以前往查看前端基础学习--带你夯实JavaScript基础 目录 一. BOM模型 1.1 BOM模型与DOM模型示意图: 1.2 浏览器窗口中的B ...

  4. keras 香草编码器_用香草javascript遍历dom

    keras 香草编码器 If you're new to JavaScript, you're likely starting out with Vanilla JavaScript in order ...

  5. 遍历同辈节电的方法_JQuery遍历DOM节点的方法

    本文实例讲述了JQuery遍历DOM节点的方法.分享给大家供大家参考.具体分析如下: 本节的核心是介绍JQuery的DOM操作,前面介绍了很多创建.删除.替换等等节点操作.这里介绍如何遍历节点,选中临 ...

  6. Vue学习笔记入门篇——数据及DOM

    本文为转载,原文:Vue学习笔记入门篇--数据及DOM 数据 data 类型 Object | Function 详细 Vue 实例的数据对象.Vue 将会递归将 data 的属性转换为 getter ...

  7. dom文档对象手册_编程小白网页学习笔记之文档对象模型(DOM)

    经过一周的学习,我这个前端小白对网页的学习,又有了新的进展,这不过来跟大家分享我的心得了.这次要分享的,主要就是我的学习的途径.上次很随意地开始了一个网页代码的初印象,还记得上次写的那几行小代码嘛,那 ...

  8. 你可能不知道的JavaScript 遍历DOM的几种方法

    最近看到一篇关于前端优化方面的文章,里面提到了几点对DOM遍历操作的优化,文章只是一笔带过,并没有深入的讲解. 其中提到了几个优化的手段,乍一看似乎没见过,然后再仔细想想,其实无论是犀牛书还是红宝书都 ...

  9. react 学习(一) 实现简版虚拟 dom 和挂载

    楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试的也都能答出来.但对于 react 只是 ...

最新文章

  1. Pandas 基础 (4)—— 汇总和计算描述统计
  2. 支付宝被曝光了一段视频,或许“刷脸支付”的时代就要来临了
  3. ad中电容用什么封装_干货 | 为什么单相电机要用电容,三相电机不需要电容?...
  4. 2017年我国SAP行业三大利好
  5. 机房收费系统——总结
  6. python实践心得体会_“Python自然语言实践”——总结(一),实战
  7. mysql中同一天入职怎么表示_ORACLE入职考试题及答案
  8. 如何使用html如何安装,node.js – 如何使用全球安装的grunt-html?
  9. 字符编码(ucs2 ucs4 utf)
  10. 1月13 PyTorch 中模型的使用,保存加载模型
  11. java定时器 不延时_ScheduledExecutorService 将一个定时任务延迟
  12. 事务Transaction
  13. .bin文件如何打开并使用
  14. 书单 | 深度学习修炼秘籍
  15. 牛蛙怎么做好吃 牛蛙的家常做法
  16. 上海巨人网络面试经历
  17. 门窗计算机公式,窗户的计算公式是什么
  18. 关于尾注的一些问题-上
  19. 移动互联网世代的焦虑,来自对科技范式转移视而不见
  20. UNIX下PageDown健如何捕捉

热门文章

  1. 用友U8 cloud,以应用牵引与价值驱动推进信创2.0阶段
  2. Docker Hub的使用方法
  3. 中国清朝~朝鲜地图/出版地点/日期伦敦/ 1741年
  4. 移动无限流量卡物联网卡优缺点对比!
  5. Dialog 使用方法详解
  6. 如何提高提测版本质量?
  7. 如何建设网站并与数据库相连(以access为例)
  8. 论文阅读|TransPose
  9. 网络安全是什么?网络安全工程师需要学什么?就业前景如何?附赠网络安全学习路线图
  10. 直播通用测试方法—性能测试