diff --git a/14.md b/14.md index 98837ca..94681b4 100644 --- a/14.md +++ b/14.md @@ -1,16 +1,28 @@ ## 十四、文档对象模型 +> 原文:[The Document Object Model](https://eloquentjavascript.net/14_dom.html) +> +> 译者:[飞龙](https://github.com/wizardforcel) +> +> 协议:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/) +> +> 自豪地采用[谷歌翻译](https://translate.google.cn/) +> +> 部分参考了[《JavaScript 编程精解(第 2 版)》](https://book.douban.com/subject/26707144/) + > Too bad! Same old story! Once you've finished building your house you notice you've accidentally learned something that you really should have known—before you started. > > Friedrich Nietzsche,《Beyond Good and Evil》 -当你在浏览器中打开网页时,浏览器会接收网页的HTML文本并进行解析,其解析方式与第11章中介绍的解析器非常相似。浏览器构建文档结构的模型,并使用该模型在屏幕上绘制页面。 + -JavaScript在其沙箱中提供了将文本转换成文档对象模型的功能。它是你可以读取或者修改的数据结构。模型是一个所见即所得的数据结构,改变模型会使得屏幕上的页面产生相应变化。 +当你在浏览器中打开网页时,浏览器会接收网页的 HTML 文本并进行解析,其解析方式与第 11 章中介绍的解析器非常相似。浏览器构建文档结构的模型,并使用该模型在屏幕上绘制页面。 + +JavaScript 在其沙箱中提供了将文本转换成文档对象模型的功能。它是你可以读取或者修改的数据结构。模型是一个所见即所得的数据结构,改变模型会使得屏幕上的页面产生相应变化。 ## 文档结构 -你可以将HTML文件想象成一系列嵌套的箱子。诸如<body>和</body>之类的标签会将其他标签包围起来,而包含在内部的标签也可以包含其他的标签和文本。这里给出上一章中已经介绍过的示例文件。 +你可以将 HTML 文件想象成一系列嵌套的箱子。诸如`
`和``之类的标签会将其他标签包围起来,而包含在内部的标签也可以包含其他的标签和文本。这里给出上一章中已经介绍过的示例文件。 ```html @@ -29,55 +41,55 @@ JavaScript在其沙箱中提供了将文本转换成文档对象模型的功能 该页面结构如下所示。 - + -浏览器使用与该形状对应的数据结构来表示文档。每个盒子都是一个对象,我们可以和这些对象交互,找出其中包含的盒子与文本。我们将这种表示方式称为文档对象模型(Document Object Model),或简称DOM。 +浏览器使用与该形状对应的数据结构来表示文档。每个盒子都是一个对象,我们可以和这些对象交互,找出其中包含的盒子与文本。我们将这种表示方式称为文档对象模型(Document Object Model),或简称 DOM。 -我们可以通过全局绑定document来访问这些对象。该对象的documentElement属性引用了表示<html>标签的对象。由于每个 HTML 文档都有一个头部和一个主体,它还具有`head`和`body`属性,指向这些元素。 +我们可以通过全局绑定`document`来访问这些对象。该对象的`documentElement`属性引用了``标签对象。由于每个 HTML 文档都有一个头部和一个主体,它还具有`head`和`body`属性,指向这些元素。 ## 树 -回想一下第12章中提到的语法树。其结构与浏览器文档的结构极为相似。每个节点可以使用children引用其他节点,而每个子节点又有各自的children。其形状是一种典型的嵌套结构,每个元素可以包含与其自身相似的子元素。 +回想一下第 12 章中提到的语法树。其结构与浏览器文档的结构极为相似。每个节点使用`children`引用其他节点,而每个子节点又有各自的`children`。其形状是一种典型的嵌套结构,每个元素可以包含与其自身相似的子元素。 -如果一个数据结构有分支结构,而且没有任何回环(一个节点不能直接或间接包含自身),并且有一个单一、定义明确的“根节点”,那么我们将这种数据结构称之为树。就DOM来讲,document.documentElement就是其根节点。 +如果一个数据结构有分支结构,而且没有任何环路(一个节点不能直接或间接包含自身),并且有一个单一、定义明确的“根节点”,那么我们将这种数据结构称之为树。就 DOM 来讲,`document.documentElement`就是其根节点。 -在计算机科学中,树的应用极为广泛。除了表现诸如HTML文档或程序之类的递归结构,树还可以用于维持数据的有序集合,因为在树中寻找或插入一个节点往往比在数组中更高效。 +在计算机科学中,树的应用极为广泛。除了表现诸如 HTML 文档或程序之类的递归结构,树还可以用于维持数据的有序集合,因为在树中寻找或插入一个节点往往比在数组中更高效。 -一棵典型的树有不同类型的节点。Egg 语言的语法树有标识符、值和应用节点。应用节点常常包含子节点,而标识符、值则是叶子节点,也就是没有孩子的节点。 +一棵典型的树有不同类型的节点。Egg 语言的语法树有标识符、值和应用节点。应用节点常常包含子节点,而标识符、值则是叶子节点,也就是没有子节点的节点。 -DOM中也是一样。元素(表示 HTML 标签)的节点用于确定文档结构。这些节点可以包含子节点。这类节点中的一个例子是document.body。其中一些子节点可以是叶子节点,比如文本片段或注释。 +DOM中也是一样。元素(表示 HTML 标签)的节点用于确定文档结构。这些节点可以包含子节点。这类节点中的一个例子是`document.body`。其中一些子节点可以是叶子节点,比如文本片段或注释。 -每个DOM节点对象都包含nodeType属性,该属性包含一个标识节点类型的代码(数字)。元素的值为1,DOM也将该值定义成一个常量属性document.ELEMENT_NODE。文本节点(表示文档中的一段文本)代码为3(document.TEXT_NODE)。注释的代码为8(document.COMMENT_NODE)。 +每个 DOM 节点对象都包含`nodeType`属性,该属性包含一个标识节点类型的代码(数字)。元素的值为 1,DOM 也将该值定义成一个常量属性`document.ELEMENT_NODE`。文本节点(表示文档中的一段文本)代码为 3(`document.TEXT_NODE`)。注释的代码为 8(`document.COMMENT_NODE`)。 因此我们可以使用另一种方法来表示文档树: - + 叶子节点是文本节点,而箭头则指出了节点之间的父子关系。 ## 标准 -并非只有JavaScript会使用数字代码来表示节点类型。本章随后将会展示其他的DOM接口,你可能会觉得这些接口有些奇怪。这是因为DOM并不是为JavaScript而设计的,它尝试成为一组语言中立的接口,确保也可用于其他系统中,不只是HTML,还有XML。XML是一种通用数据格式,语法与HTML相近。 +并非只有 JavaScript 会使用数字代码来表示节点类型。本章随后将会展示其他的 DOM 接口,你可能会觉得这些接口有些奇怪。这是因为 DOM 并不是为 JavaScript 而设计的,它尝试成为一组语言中立的接口,确保也可用于其他系统中,不只是 HTML,还有 XML。XML 是一种通用数据格式,语法与 HTML 相近。 这就比较糟糕了。一般情况下标准都是非常易于使用的。但在这里其优势(跨语言的一致性)并不明显。相较于为不同语言提供类似的接口,如果能够将接口与开发者使用的语言进行适当集成,可以为开发者节省大量时间。 -我们举例来说明一下集成问题。比如DOM中每个元素都有childNodes属性。该属性是一个类似于数组的对象,有length属性,也可以使用数字标签访问对应的子节点。但该属性是NodeList类型的实例,而不是真正的数组,因此该类型没有诸如slice和map之类的方法。 +我们举例来说明一下集成问题。比如 DOM 中每个元素都有`childNodes`属性。该属性是一个类数组对象,有`length`属性,也可以使用数字标签访问对应的子节点。但该属性是`NodeList`类型的实例,而不是真正的数组,因此该类型没有诸如`slice`和`map`之类的方法。 -有些问题是由不好的设计导致的。例如,我们无法在创建新的节点的同时立即为其添加孩子和属性。相反,你首先需要创建节点,然后使用副作用,将子节点和属性逐个添加到节点中。大量使用DOM的代码通常较长、重复和丑陋。 +有些问题是由不好的设计导致的。例如,我们无法在创建新的节点的同时立即为其添加子节点和属性。相反,你首先需要创建节点,然后使用副作用,将子节点和属性逐个添加到节点中。大量使用 DOM 的代码通常较长、重复和丑陋。 -但这些问题并非无法改善。因为JavaScript允许我们构建自己的抽象,可以设计改进方式来表达您正在执行的操作。 许多用于浏览器编程的库都附带这些工具。 +但这些问题并非无法改善。因为 JavaScript 允许我们构建自己的抽象,可以设计改进方式来表达你正在执行的操作。 许多用于浏览器编程的库都附带这些工具。 ## 沿着树移动 -DOM节点包含了许多指向相邻节点的链接。下面的图表展示了这一点。 +DOM 节点包含了许多指向相邻节点的链接。下面的图表展示了这一点。 - + -尽管图表中每种类型的节点只显示出一条链接,但每个节点都有parentNode属性,指向一个节点,它是这个节点的一部分。类似的,每个元素节点(节点类型为1)均包含childNodes属性,该属性指向一个类似于数组的对象,用于保存其子节点。 +尽管图表中每种类型的节点只显示出一条链接,但每个节点都有`parentNode`属性,指向一个节点,它是这个节点的一部分。类似的,每个元素节点(节点类型为 1)均包含`childNodes`属性,该属性指向一个类数组对象,用于保存其子节点。 -理论上,你可以通过父子之间的链接移动到树中的任何地方。但JavaScript也提供了一些更加方便的额外链接。firstChild属性和lastChild属性分别指向第一个孩子和最后一个孩子,若没有孩子则值为null。类似的,previousSibling和nextSibling指向相邻节点,分别指向拥有相同父亲的前一个节点和后一个节点。对于第一个孩子,previousSibling是null,而最后一个孩子的nextSibling则是null。 +理论上,你可以通过父子之间的链接移动到树中的任何地方。但 JavaScript 也提供了一些更加方便的额外链接。`firstChild`属性和`lastChild`属性分别指向第一个子节点和最后一个子节点,若没有子节点则值为`null`。类似的,`previousSibling`和`nextSibling`指向相邻节点,分别指向拥有相同父亲的前一个节点和后一个节点。对于第一个子节点,`previousSibling`是`null`,而最后一个子节点的`nextSibling`则是`null`。 -也存在`children`属性,它就像`childNodes`,但只包含元素(类型为 1)的子节点,而不包含其他类型的子节点。 当您对文本节点不感兴趣时,这可能很有用。 +也存在`children`属性,它就像`childNodes`,但只包含元素(类型为 1)子节点,而不包含其他类型的子节点。 当你对文本节点不感兴趣时,这可能很有用。 处理像这样的嵌套数据结构时,递归函数通常很有用。 以下函数在文档中扫描包含给定字符串的文本节点,并在找到一个时返回`true`: @@ -105,18 +117,18 @@ console.log(talksAbout(document.body, "book")); ## 查找元素 -使用父节点、子节点和兄弟节点之间的连接遍历节点确实非常实用。但是如果我们只想查找文档中的特定节点,那么从document.body开始盲目沿着硬编码的链接路径查找节点并非良策。如果程序通过树结构定位节点,就需要依赖于文档的具体结构,而文档结构随后可能发生变化。另一个复杂的因素是DOM会为不同节点之间的空白字符创建对应的文本节点。例如示例文档中的body标签不止包含3个孩子(<h1>和两个<p>元素),其实包含7个孩子:这三个节点、三个节点前后的空格、以及元素之间的空格。 +使用父节点、子节点和兄弟节点之间的连接遍历节点确实非常实用。但是如果我们只想查找文档中的特定节点,那么从`document.body`开始盲目沿着硬编码的链接路径查找节点并非良策。如果程序通过树结构定位节点,就需要依赖于文档的具体结构,而文档结构随后可能发生变化。另一个复杂的因素是 DOM 会为不同节点之间的空白字符创建对应的文本节点。例如示例文档中的`body`标签不止包含 3 个子节点(``元素),其实包含 7 个子节点:这三个节点、三个节点前后的空格、以及元素之间的空格。 -因此,如果你想获取文档中某个链接的href属性,最好不要去获取文档body元素中第六个孩子的第二个孩子,而最好直接获取文档中的第一个链接,而且这样的操作确实可以实现。 +因此,如果你想获取文档中某个链接的`href`属性,最好不要去获取文档`body`元素中第六个子节点的第二个子节点,而最好直接获取文档中的第一个链接,而且这样的操作确实可以实现。 ```html let link = document.body.getElementsByTagName("a")[0]; console.log(link.href); ``` -所有元素节点都包含getElementsByTagName方法,用于从所有后代节点中(直接或间接子节点)搜索包含给定标签名的节点,并返回一个类数组的对象。 +所有元素节点都包含`getElementsByTagName`方法,用于从所有后代节点中(直接或间接子节点)搜索包含给定标签名的节点,并返回一个类数组的对象。 -你也可以使用document.getElementById来寻找包含特定id属性的某个节点。 +你也可以使用`document.getElementById`来寻找包含特定`id`属性的某个节点。 ```html
My ostrich Gertrude:
@@ -128,11 +140,11 @@ console.log(link.href); ``` -第三个类似的方法是getElementsByClassName,它与getElementsByTagName类似,会搜索元素节点的内容并获取所有包含特定class属性的元素。 +第三个类似的方法是`getElementsByClassName`,它与`getElementsByTagName`类似,会搜索元素节点的内容并获取所有包含特定`class`属性的元素。 ## 修改文档 -几乎所有DOM数据结构中的元素都可以被修改。文档树的形状可以通过改变父子关系来修改。 节点的`remove`方法将它们从当前父节点中移除。appendChild方法可以添加子节点,并将其放置在子节点列表末尾,而insertBefore则将第一个参数表示的节点插入到第二个参数表示的节点前面。 +几乎所有 DOM 数据结构中的元素都可以被修改。文档树的形状可以通过改变父子关系来修改。 节点的`remove`方法将它们从当前父节点中移除。`appendChild`方法可以添加子节点,并将其放置在子节点列表末尾,而`insertBefore`则将第一个参数表示的节点插入到第二个参数表示的节点前面。 ```htmlOne
@@ -145,15 +157,15 @@ console.log(link.href); ``` -每个节点只能存在于文档中的某一个位置。因此,如果将段落Three插入到段落One前,会将该节点从文档末尾移除并插入到文档前面,最后结果为“Three/One/Two”。所有将节点插入到某处的方法都有这种副作用——会将其从当前位置移除(如果存在的话)。 +每个节点只能存在于文档中的某一个位置。因此,如果将段落`Three`插入到段落`One`前,会将该节点从文档末尾移除并插入到文档前面,最后结果为`Three/One/Two`。所有将节点插入到某处的方法都有这种副作用——会将其从当前位置移除(如果存在的话)。 -replaceChild方法用于将一个子节点替换为另一个子节点。该方法接受两个参数,第一个参数是新节点,第二个参数是待替换的节点。待替换的节点必须是该方法调用者的子节点。这里需要注意,replaceChild和insertBefore都将新节点作为第一个参数。 +`replaceChild`方法用于将一个子节点替换为另一个子节点。该方法接受两个参数,第一个参数是新节点,第二个参数是待替换的节点。待替换的节点必须是该方法调用者的子节点。这里需要注意,`replaceChild`和`insertBefore`都将新节点作为第一个参数。 ## 创建节点 假设我们要编写一个脚本,将文档中的所有图像(`The in the
@@ -178,7 +190,7 @@ replaceChild方法用于将一个子节点替换为另一个子节点。该方
给定一个字符串,`createTextNode`为我们提供了一个文本节点,我们可以将它插入到文档中,来使其显示在屏幕上。
-该循环从列表末尾开始遍历图像。我们必须这样反向遍历列表,因为getElementsByTagName之类的方法返回的节点列表是动态变化的。该列表会随着文档改变还改变。若我们从列表头开始遍历,移除掉第一个图像会导致列表丢失其第一个元素,第二次循环时,因为集合的长度此时为1,而i也为1,所以循环会停止。
+该循环从列表末尾开始遍历图像。我们必须这样反向遍历列表,因为`getElementsByTagName`之类的方法返回的节点列表是动态变化的。该列表会随着文档改变还改变。若我们从列表头开始遍历,移除掉第一个图像会导致列表丢失其第一个元素,第二次循环时,因为集合的长度此时为 1,而`i`也为 1,所以循环会停止。
如果你想要获得一个固定的节点集合,可以使用数组的`Array.from`方法将其转换成实际数组。
@@ -189,9 +201,9 @@ console.log(array.map(s => s.toUpperCase()));
// → ["ONE", "TWO"]
```
-你可以使用document.createElement方法创建一个元素节点。该方法接受一个标签名,返回一个新的空节点,节点类型由标签名指定。
+你可以使用`document.createElement`方法创建一个元素节点。该方法接受一个标签名,返回一个新的空节点,节点类型由标签名指定。
-下面的示例定义了一个elt工具,用于创建一个新的元素节点,并将其剩余参数当作该节点的子节点。接着使用该函数为引用添加来源信息。
+下面的示例定义了一个`elt`工具,用于创建一个新的元素节点,并将其剩余参数当作该节点的子节点。接着使用该函数为引用添加来源信息。
```html
@@ -222,9 +234,9 @@ console.log(array.map(s => s.toUpperCase()));
## 属性
-我们可以通过元素的DOM对象的同名属性去访问元素的某些属性,比如链接的href属性。这仅限于最常用的标准属性。
+我们可以通过元素的 DOM 对象的同名属性去访问元素的某些属性,比如链接的`href`属性。这仅限于最常用的标准属性。
-HTML允许你在节点上设定任何属性。这一特性非常有用,因为这样你就可以在文档中存储额外信息。你自己创建的属性不会出现在元素节点的属性中。你必须使用getAttribute和setAttribute方法来访问这些属性。
+HTML 允许你在节点上设定任何属性。这一特性非常有用,因为这样你就可以在文档中存储额外信息。你自己创建的属性不会出现在元素节点的属性中。你必须使用`getAttribute`和`setAttribute`方法来访问这些属性。
```html
The launch code is 00000000.
@@ -242,17 +254,17 @@ HTML允许你在节点上设定任何属性。这一特性非常有用,因为 建议为这些组合属性的名称添加`data-`前缀,来确保它们不与任何其他属性发生冲突。 -这里有一个常用的属性:class。该属性是JavaScript中的保留字。因为某些历史原因(某些旧版本的JavaScript实现无法处理和关键字或保留字同名的属性),访问class的属性名为className。你也可以使用getAttribute和setAttribute方法,使用其实际名称“class”来访问该属性。 +这里有一个常用的属性:`class`。该属性是 JavaScript 中的保留字。因为某些历史原因(某些旧版本的 JavaScript 实现无法处理和关键字或保留字同名的属性),访问`class`的属性名为`className`。你也可以使用`getAttribute`和`setAttribute`方法,使用其实际名称`class`来访问该属性。 ## 布局 -你可能已经注意到不同类型的元素有不同的布局。某些元素,比如段落(<p>)和标题(<h1>)会占据整个文档的宽度,并且在独立的一行中渲染。这些元素被称为块(Block)元素。其他的元素,比如链接(<a>或<strong>元素则与周围文本在同一行中渲染。这类元素我们称之为内联(Inline)元素。 +你可能已经注意到不同类型的元素有不同的布局。某些元素,比如段落(``)和标题(`
@@ -305,18 +317,18 @@ getBoundingClientRect方法是获取屏幕中某个元素精确位置的最有 ## 样式 -我们看到了不同的HTML元素的绘制是不同的。一些元素显示为块,一些则是以内联方式显示。我们还可以添加一些样式,比如使用<strong>加粗内容,或使用<a>使内容变成蓝色,并添加下划线。 +我们看到了不同的 HTML 元素的绘制是不同的。一些元素显示为块,一些则是以内联方式显示。我们还可以添加一些样式,比如使用``加粗内容,或使用``使内容变成蓝色,并添加下划线。 -<img>标签显示图片的方式或点击标签<a>时跳转的链接都和元素类型紧密相关。但元素的默认样式,比如文本的颜色、是否有下划线,都是可以改变的。这里给出使用style属性的示例。 +`
@@ -340,11 +352,11 @@ JavaScript代码可以通过元素的style属性操作元素的样式。该属 ``` -一些样式属性名包含破折号,比如font-family。由于这些属性的命名不适合在JavaScript中使用(你必须写成style[“font-family”]),因此在JavaScript中,样式对象中的属性名都移除了破折号,并将破折号之后的字母大写(style.fontFamily)。 +一些样式属性名包含破折号,比如`font-family`。由于这些属性的命名不适合在 JavaScript 中使用(你必须写成`style["font-family"]`),因此在 JavaScript 中,样式对象中的属性名都移除了破折号,并将破折号之后的字母大写(`style.fontFamily`)。 ## 层叠样式 -我们把HTML的样式化系统称为CSS,即层叠样式表(Cascading Style Sheets)。样式表是一系列规则,指出如何为文档中元素添加样式。可以在<style>标签中写入CSS。 +我们把 HTML 的样式化系统称为 CSS,即层叠样式表(Cascading Style Sheets)。样式表是一系列规则,指出如何为文档中元素添加样式。可以在` @@ -580,4 +592,3 @@ document.getElementsByTagName方法返回带有特定标签名称的所有子元 requestAnimationFrame(animate); ``` - diff --git a/img/14-0.jpg b/img/14-0.jpg new file mode 100644 index 0000000..90af0d7 Binary files /dev/null and b/img/14-0.jpg differ diff --git a/img/14-1.svg b/img/14-1.svg new file mode 100644 index 0000000..f985457 --- /dev/null +++ b/img/14-1.svg @@ -0,0 +1,23 @@ + + diff --git a/img/14-2.svg b/img/14-2.svg new file mode 100644 index 0000000..c6c3510 --- /dev/null +++ b/img/14-2.svg @@ -0,0 +1,36 @@ + + diff --git a/img/14-3.svg b/img/14-3.svg new file mode 100644 index 0000000..1fc0487 --- /dev/null +++ b/img/14-3.svg @@ -0,0 +1,25 @@ + + diff --git a/img/14-4.svg b/img/14-4.svg new file mode 100644 index 0000000..bfde16f --- /dev/null +++ b/img/14-4.svg @@ -0,0 +1,11 @@ + +