DOM深入学习 --- DOM 树介绍,如何遍历 DOM 树节点(一)
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
节点(它只包含一个换行符和一些空格)。
只有两个顶级排除项:
- 由于历史原因,
<head>
之前的空格和换行符均被忽略。 - 如果我们在
</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 种:
document
— DOM 的“入口点”。- 元素节点 — HTML 标签,树构建块。
- 文本节点 — 包含文本。
- 注释 — 有时我们可以将一些信息放入其中,它不会显示,但 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>
。实际上,文档下面还有很多东西,但是在这个脚本运行的时候,浏览器还没有读到下面的内容,所以这个脚本也就看不到它们。
firstChild
和 lastChild
属性是访问第一个和最后一个子元素的快捷方式。
它们只是简写。如果元素存在子节点,那么下面的脚本运行结果将是 true:
elem.childNodes[0] === elem.firstChild
elem.childNodes[elem.childNodes.length - 1] === elem.lastChild
这里还有一个特别的函数 elem.hasChildNodes()
用于检查节点是否有子节点。
遍历 DOM 集合
正如我们看到的那样,childNodes
看起来就像一个数组。但实际上它并不是一个数组,而是一个 集合 — 一个类数组的可迭代对象。
这个性质会导致两个重要的结果:
- 我们可以使用
for..of
来迭代它:
for (let node of document.body.childNodes) {alert(node); // 显示集合中的所有节点
}
这是因为集合是可迭代的(提供了所需要的 Symbol.iterator
属性)。
- 无法使用数组的方法,因为它不是一个数组:
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)属性访问其直接的邻居。
这些属性主要分为两组:
- 对于所有节点:
parentNode
,childNodes
,firstChild
,lastChild
,previousSibling
,nextSibling
。 - 仅对于元素节点:
parentElement
,children
,firstElementChild
,lastElementChild
,previousElementSibling
,nextElementSibling
。
某些类型的 DOM 元素,例如 table,提供了用于访问其内容的其他属性和集合。
DOM深入学习 --- DOM 树介绍,如何遍历 DOM 树节点(一)相关推荐
- Html遍历dom树,jQuery向上遍历DOM树之parents(),parent(),closest()之间的区别
在这个sprint中,因为要写前端UI,所以用到了jQuery,但是jQuery在向上遍历DOM树的API中,有parents(). parent().closest()这几个,一直不太清楚它们具体的 ...
- DOM系列:DOM树和遍历DOM
上一节,咱们整理了DOM系列中的第一篇,主要介绍浏览器与DOM相关的知识.从标题中我们可以看出来,今天所要学的东西包含两个部分,第一部分是DOM树,第二部分是遍历DOM.如果你和我一样对于DOM树和遍 ...
- 前端基础学习——JavaScript之BOM模型与DOM模型
前面还有JavaScript基础介绍,有兴趣的朋友可以前往查看前端基础学习--带你夯实JavaScript基础 目录 一. BOM模型 1.1 BOM模型与DOM模型示意图: 1.2 浏览器窗口中的B ...
- keras 香草编码器_用香草javascript遍历dom
keras 香草编码器 If you're new to JavaScript, you're likely starting out with Vanilla JavaScript in order ...
- 遍历同辈节电的方法_JQuery遍历DOM节点的方法
本文实例讲述了JQuery遍历DOM节点的方法.分享给大家供大家参考.具体分析如下: 本节的核心是介绍JQuery的DOM操作,前面介绍了很多创建.删除.替换等等节点操作.这里介绍如何遍历节点,选中临 ...
- Vue学习笔记入门篇——数据及DOM
本文为转载,原文:Vue学习笔记入门篇--数据及DOM 数据 data 类型 Object | Function 详细 Vue 实例的数据对象.Vue 将会递归将 data 的属性转换为 getter ...
- dom文档对象手册_编程小白网页学习笔记之文档对象模型(DOM)
经过一周的学习,我这个前端小白对网页的学习,又有了新的进展,这不过来跟大家分享我的心得了.这次要分享的,主要就是我的学习的途径.上次很随意地开始了一个网页代码的初印象,还记得上次写的那几行小代码嘛,那 ...
- 你可能不知道的JavaScript 遍历DOM的几种方法
最近看到一篇关于前端优化方面的文章,里面提到了几点对DOM遍历操作的优化,文章只是一笔带过,并没有深入的讲解. 其中提到了几个优化的手段,乍一看似乎没见过,然后再仔细想想,其实无论是犀牛书还是红宝书都 ...
- react 学习(一) 实现简版虚拟 dom 和挂载
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试的也都能答出来.但对于 react 只是 ...
最新文章
- Pandas 基础 (4)—— 汇总和计算描述统计
- 支付宝被曝光了一段视频,或许“刷脸支付”的时代就要来临了
- ad中电容用什么封装_干货 | 为什么单相电机要用电容,三相电机不需要电容?...
- 2017年我国SAP行业三大利好
- 机房收费系统——总结
- python实践心得体会_“Python自然语言实践”——总结(一),实战
- mysql中同一天入职怎么表示_ORACLE入职考试题及答案
- 如何使用html如何安装,node.js – 如何使用全球安装的grunt-html?
- 字符编码(ucs2 ucs4 utf)
- 1月13 PyTorch 中模型的使用,保存加载模型
- java定时器 不延时_ScheduledExecutorService 将一个定时任务延迟
- 事务Transaction
- .bin文件如何打开并使用
- 书单 | 深度学习修炼秘籍
- 牛蛙怎么做好吃 牛蛙的家常做法
- 上海巨人网络面试经历
- 门窗计算机公式,窗户的计算公式是什么
- 关于尾注的一些问题-上
- 移动互联网世代的焦虑,来自对科技范式转移视而不见
- UNIX下PageDown健如何捕捉