来源: ApacheCN『JavaScript 编程精解 中文第三版』翻译项目原文:The Document Object Modeljavascript
译者:飞龙css
协议:CC BY-NC-SA 4.0html
自豪地采用谷歌翻译java
部分参考了《JavaScript 编程精解(第 2 版)》node
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.git
Friedrich Nietzsche,《Beyond Good and Evil》github
当你在浏览器中打开网页时,浏览器会接收网页的 HTML 文本并进行解析,其解析方式与第 11 章中介绍的解析器很是类似。浏览器构建文档结构的模型,并使用该模型在屏幕上绘制页面。apache
JavaScript 在其沙箱中提供了将文本转换成文档对象模型的功能。它是你能够读取或者修改的数据结构。模型是一个所见即所得的数据结构,改变模型会使得屏幕上的页面产生相应变化。编程
你能够将 HTML 文件想象成一系列嵌套的箱子。诸如<body>
和</body>
之类的标签会将其余标签包围起来,而包含在内部的标签也能够包含其余的标签和文本。这里给出上一章中已经介绍过的示例文件。数组
<!doctype html> <html> <head> <title>My home page</title> </head> <body> <h1>My home page</h1> <p>Hello, I am Marijn and this is my home page.</p> <p>I also wrote a book! Read it <a href="http://eloquentjavascript.net">here</a>.</p> </body> </html>
该页面结构以下所示。
浏览器使用与该形状对应的数据结构来表示文档。每一个盒子都是一个对象,咱们能够和这些对象交互,找出其中包含的盒子与文本。咱们将这种表示方式称为文档对象模型(Document Object Model),或简称 DOM。
咱们能够经过全局绑定document
来访问这些对象。该对象的documentElement
属性引用了<html>
标签对象。因为每一个 HTML 文档都有一个头部和一个主体,它还具备head
和body
属性,指向这些元素。
回想一下第 12 章中提到的语法树。其结构与浏览器文档的结构极为类似。每一个节点使用children
引用其余节点,而每一个子节点又有各自的children
。其形状是一种典型的嵌套结构,每一个元素能够包含与其自身类似的子元素。
若是一个数据结构有分支结构,并且没有任何环路(一个节点不能直接或间接包含自身),而且有一个单1、定义明确的“根节点”,那么咱们将这种数据结构称之为树。就 DOM 来说,document.documentElement
就是其根节点。
在计算机科学中,树的应用极为普遍。除了表现诸如 HTML 文档或程序之类的递归结构,树还能够用于维持数据的有序集合,由于在树中寻找或插入一个节点每每比在数组中更高效。
一棵典型的树有不一样类型的节点。Egg 语言的语法树有标识符、值和应用节点。应用节点经常包含子节点,而标识符、值则是叶子节点,也就是没有子节点的节点。
DOM中也是同样。元素(表示 HTML 标签)的节点用于肯定文档结构。这些节点能够包含子节点。这类节点中的一个例子是document.body
。其中一些子节点能够是叶子节点,好比文本片断或注释。
每一个 DOM 节点对象都包含nodeType
属性,该属性包含一个标识节点类型的代码(数字)。元素的值为 1,DOM 也将该值定义成一个常量属性document.ELEMENT_NODE
。文本节点(表示文档中的一段文本)代码为 3(document.TEXT_NODE
)。注释的代码为 8(document.COMMENT_NODE
)。
所以咱们可使用另外一种方法来表示文档树:
叶子节点是文本节点,而箭头则指出了节点之间的父子关系。
并不是只有 JavaScript 会使用数字代码来表示节点类型。本章随后将会展现其余的 DOM 接口,你可能会以为这些接口有些奇怪。这是由于 DOM 并非为 JavaScript 而设计的,它尝试成为一组语言中立的接口,确保也可用于其余系统中,不仅是 HTML,还有 XML。XML 是一种通用数据格式,语法与 HTML 相近。
这就比较糟糕了。通常状况下标准都是很是易于使用的。但在这里其优点(跨语言的一致性)并不明显。相较于为不一样语言提供相似的接口,若是可以将接口与开发者使用的语言进行适当集成,能够为开发者节省大量时间。
咱们举例来讲明一下集成问题。好比 DOM 中每一个元素都有childNodes
属性。该属性是一个类数组对象,有length
属性,也可使用数字标签访问对应的子节点。但该属性是NodeList
类型的实例,而不是真正的数组,所以该类型没有诸如slice
和map
之类的方法。
有些问题是由很差的设计致使的。例如,咱们没法在建立新的节点的同时当即为其添加子节点和属性。相反,你首先须要建立节点,而后使用反作用,将子节点和属性逐个添加到节点中。大量使用 DOM 的代码一般较长、重复和丑陋。
但这些问题并不是没法改善。由于 JavaScript 容许咱们构建本身的抽象,能够设计改进方式来表达你正在执行的操做。 许多用于浏览器编程的库都附带这些工具。
DOM 节点包含了许多指向相邻节点的连接。下面的图表展现了这一点。
尽管图表中每种类型的节点只显示出一条连接,但每一个节点都有parentNode
属性,指向一个节点,它是这个节点的一部分。相似的,每一个元素节点(节点类型为 1)均包含childNodes
属性,该属性指向一个类数组对象,用于保存其子节点。
理论上,你能够经过父子之间的连接移动到树中的任何地方。但 JavaScript 也提供了一些更加方便的额外连接。firstChild
属性和lastChild
属性分别指向第一个子节点和最后一个子节点,若没有子节点则值为null
。相似的,previousSibling
和nextSibling
指向相邻节点,分别指向拥有相同父亲的前一个节点和后一个节点。对于第一个子节点,previousSibling
是null
,而最后一个子节点的nextSibling
则是null
。
也存在children
属性,它就像childNodes
,但只包含元素(类型为 1)子节点,而不包含其余类型的子节点。 当你对文本节点不感兴趣时,这可能颇有用。
处理像这样的嵌套数据结构时,递归函数一般颇有用。 如下函数在文档中扫描包含给定字符串的文本节点,并在找到一个时返回true
:
function talksAbout(node, string) { if (node.nodeType == document.ELEMENT_NODE) { for (let i = 0; i < node.childNodes.length; i++) { if (talksAbout(node.childNodes[i], string)) { return true; } } return false; } else if (node.nodeType == document.TEXT_NODE) { return node.nodeValue.indexOf(string) > -1; } } console.log(talksAbout(document.body, "book")); // → true
由于childNodes
不是真正的数组,因此咱们不能用for/of
来遍历它,而且必须使用普通的for
循环遍历索引范围。
文本节点的nodeValue
属性保存它所表示的文本字符串。
使用父节点、子节点和兄弟节点之间的链接遍历节点确实很是实用。可是若是咱们只想查找文档中的特定节点,那么从document.body
开始盲目沿着硬编码的连接路径查找节点并不是良策。若是程序经过树结构定位节点,就须要依赖于文档的具体结构,而文档结构随后可能发生变化。另外一个复杂的因素是 DOM 会为不一样节点之间的空白字符建立对应的文本节点。例如示例文档中的body
标签不止包含 3 个子节点(<h1>
和两个<p>
元素),其实包含 7 个子节点:这三个节点、三个节点先后的空格、以及元素之间的空格。
所以,若是你想获取文档中某个连接的href
属性,最好不要去获取文档body
元素中第六个子节点的第二个子节点,而最好直接获取文档中的第一个连接,并且这样的操做确实能够实现。
let link = document.body.getElementsByTagName("a")[0]; console.log(link.href);
全部元素节点都包含getElementsByTagName
方法,用于从全部后代节点中(直接或间接子节点)搜索包含给定标签名的节点,并返回一个类数组的对象。
你也可使用document.getElementById
来寻找包含特定id
属性的某个节点。
<p>My ostrich Gertrude:</p> <p><img id="gertrude" src="https://gitee.com/wizardforcel/eloquent-js-3e-zh/raw/master/img/ostrich.png"></p> <script> let ostrich = document.getElementById("gertrude"); console.log(ostrich.src); </script>
第三个相似的方法是getElementsByClassName
,它与getElementsByTagName
相似,会搜索元素节点的内容并获取全部包含特定class
属性的元素。
几乎全部 DOM 数据结构中的元素均可以被修改。文档树的形状能够经过改变父子关系来修改。 节点的remove
方法将它们从当前父节点中移除。appendChild
方法能够添加子节点,并将其放置在子节点列表末尾,而insertBefore
则将第一个参数表示的节点插入到第二个参数表示的节点前面。
<p>One</p> <p>Two</p> <p>Three</p> <script> let paragraphs = document.body.getElementsByTagName("p"); document.body.insertBefore(paragraphs[2], paragraphs[0]); </script>
每一个节点只能存在于文档中的某一个位置。所以,若是将段落Three
插入到段落One
前,会将该节点从文档末尾移除并插入到文档前面,最后结果为Three/One/Two
。全部将节点插入到某处的方法都有这种反作用——会将其从当前位置移除(若是存在的话)。
replaceChild
方法用于将一个子节点替换为另外一个子节点。该方法接受两个参数,第一个参数是新节点,第二个参数是待替换的节点。待替换的节点必须是该方法调用者的子节点。这里须要注意,replaceChild
和insertBefore
都将新节点做为第一个参数。
假设咱们要编写一个脚本,将文档中的全部图像(<img>
标签)替换为其alt
属性中的文本,该文本指定了图像的文字替表明示。
这不只涉及删除图像,还涉及添加新的文本节点,并替换原有图像节点。为此咱们使用document.createTextNode
方法。
<p>The <img src="https://gitee.com/wizardforcel/eloquent-js-3e-zh/raw/master/img/cat.png" alt="Cat"> in the <img src="https://gitee.com/wizardforcel/eloquent-js-3e-zh/raw/master/img/hat.png" alt="Hat">.</p> <p><button onclick="replaceImages()">Replace</button></p> <script> function replaceImages() { let images = document.body.getElementsByTagName("img"); for (let i = images.length - 1; i >= 0; i--) { let image = images[i]; var image = images[i]; if (image.alt) { let text = document.createTextNode(image.alt); image.parentNode.replaceChild(text, image); } } } </script>
给定一个字符串,createTextNode
为咱们提供了一个文本节点,咱们能够将它插入到文档中,来使其显示在屏幕上。
该循环从列表末尾开始遍历图像。咱们必须这样反向遍历列表,由于getElementsByTagName
之类的方法返回的节点列表是动态变化的。该列表会随着文档改变还改变。若咱们从列表头开始遍历,移除掉第一个图像会致使列表丢失其第一个元素,第二次循环时,由于集合的长度此时为 1,而i
也为 1,因此循环会中止。
若是你想要得到一个固定的节点集合,可使用数组的Array.from
方法将其转换成实际数组。
let arrayish = {0: "one", 1: "two", length: 2}; let array = Array.from(arrayish); console.log(array.map(s => s.toUpperCase())); // → ["ONE", "TWO"]
你可使用document.createElement
方法建立一个元素节点。该方法接受一个标签名,返回一个新的空节点,节点类型由标签名指定。
下面的示例定义了一个elt
工具,用于建立一个新的元素节点,并将其剩余参数看成该节点的子节点。接着使用该函数为引用添加来源信息。
<blockquote id="quote"> No book can ever be finished. While working on it we learn just enough to find it immature the moment we turn away from it. </blockquote> <script> function elt(type, ...children) { let node = document.createElement(type); for (let child of children) { if (typeof child != "string") node.appendChild(child); else node.appendChild(document.createTextNode(child)); } return node; } document.getElementById("quote").appendChild( elt("footer", "—", elt("strong", "Karl Popper"), ", preface to the second editon of ", elt("em", "The Open Society and Its Enemies"), ", 1950")); </script>
咱们能够经过元素的 DOM 对象的同名属性去访问元素的某些属性,好比连接的href
属性。这仅限于最经常使用的标准属性。
HTML 容许你在节点上设定任何属性。这一特性很是有用,由于这样你就能够在文档中存储额外信息。你本身建立的属性不会出如今元素节点的属性中。你必须使用getAttribute
和setAttribute
方法来访问这些属性。
<p data-classified="secret">The launch code is 00000000.</p> <p data-classified="unclassified">I have two feet.</p> <script> let paras = document.body.getElementsByTagName("p"); for (let para of Array.from(paras)) { if (para.getAttribute("data-classified") == "secret") { para.remove(); } } </script>
建议为这些组合属性的名称添加data-
前缀,来确保它们不与任何其余属性发生冲突。
这里有一个经常使用的属性:class
。该属性是 JavaScript 中的保留字。由于某些历史缘由(某些旧版本的 JavaScript 实现没法处理和关键字或保留字同名的属性),访问class
的属性名为className
。你也可使用getAttribute
和setAttribute
方法,使用其实际名称class
来访问该属性。
你可能已经注意到不一样类型的元素有不一样的布局。某些元素,好比段落(<p>
)和标题(<h1>
)会占据整个文档的宽度,而且在独立的一行中渲染。这些元素被称为块(Block)元素。其余的元素,好比连接(<a>
或<strong>
元素则与周围文本在同一行中渲染。这类元素咱们称之为内联(Inline)元素。
对于任意特定文档,浏览器能够根据每一个元素的类型和内容计算其尺寸与位置等布局信息。接着使用布局来绘制文档。
JavaScript 中能够访问元素的尺寸与位置。
属性offsetWidth
和offsetHeight
给出元素的起始位置(单位是像素)。像素是浏览器中的基本测量单元。它一般对应于屏幕能够绘制的最小的点,可是在现代显示器上,能够绘制很是小的点,这可能再也不适用了,而且浏览器像素可能跨越多个显示点。
一样,clientWidth
和clientHeight
向你提供元素内的空间大小,忽略边框宽度。
<p style="border: 3px solid red"> I'm boxed in </p> <script> let para = document.body.getElementsByTagName("p")[0]; console.log("clientHeight:", para.clientHeight); console.log("offsetHeight:", para.offsetHeight); </script>
getBoundingClientRect
方法是获取屏幕中某个元素精确位置的最有效方法。该方法返回一个对象,包含top
、bottom
、left
和right
四个属性,表示元素相对于屏幕左上角的位置(单位是像素)。若你想要知道其相对于整个文档的位置,必须加上其滚动位置,你能够在pageXOffset
和pageYOffset
绑定中找到。
咱们还须要花些力气才能完成文档的排版工做。为了加快速度,每次你改变它时,浏览器引擎不会当即从新绘制整个文档,而是尽量等待并推迟重绘操做。当一个修改文档的 JavaScript 程序结束时,浏览器会计算新的布局,并在屏幕上显示修改过的文档。若程序经过读取offsetHeight
和getBoundingClientRect
这类属性获取某些元素的位置或尺寸时,为了提供正确的信息,浏览器也须要计算布局。
若是程序反复读取 DOM 布局信息或修改 DOM,会强制引起大量布局计算,致使运行很是缓慢。下面的代码展现了一个示例。该示例包含两个不一样的程序,使用X
字符构建一条线,其长度是 2000 像素,并计算每一个任务的时间。
<p><span id="one"></span></p> <p><span id="two"></span></p> <script> function time(name, action) { let start = Date.now(); // Current time in milliseconds action(); console.log(name, "took", Date.now() - start, "ms"); } time("naive", () => { let target = document.getElementById("one"); while (target.offsetWidth < 2000) { target.appendChild(document.createTextNode("X")); } }); // → naive took 32 ms time("clever", function() { let target = document.getElementById("two"); target.appendChild(document.createTextNode("XXXXX")); let total = Math.ceil(2000 / (target.offsetWidth / 5)); target.firstChild.nodeValue = "X".repeat(total); }); // → clever took 1 ms </script>
咱们看到了不一样的 HTML 元素的绘制是不一样的。一些元素显示为块,一些则是之内联方式显示。咱们还能够添加一些样式,好比使用<strong>
加粗内容,或使用<a>
使内容变成蓝色,并添加下划线。
<img>
标签显示图片的方式或点击标签<a>
时跳转的连接都和元素类型紧密相关。但元素的默认样式,好比文本的颜色、是否有下划线,都是能够改变的。这里给出使用style
属性的示例。
<p><a href=".">Normal link</a></p> <p><a href="." style="color: green">Green link</a></p>
样式属性能够包含一个或多个声明,格式为属性(好比color
)后跟着一个冒号和一个值(好比green
)。当包含更多声明时,不一样属性之间必须使用分号分隔,好比color:red;border:none
。
文档的不少方面会受到样式的影响。例如,display
属性控制一个元素是否显示为块元素或内联元素。
This text is displayed <strong>inline</strong>, <strong style="display: block">as a block</strong>, and <strong style="display: none">not at all</strong>.
block
标签会结束其所在的那一行,由于块元素是不会和周围文本内联显示的。最后一个标签彻底不会显示出来,由于display:none
会阻止一个元素呈如今屏幕上。这是隐藏元素的一种方式。更好的方式是将其从文档中彻底移除,由于稍后将其放回去是一件很简单的事情。
JavaScript 代码能够经过元素的style
属性操做元素的样式。该属性保存了一个对象,对象中存储了全部可能的样式属性,这些属性的值是字符串,咱们能够把字符串写入属性,修改某些方面的元素样式。
<p id="para" style="color: purple"> Nice text </p> <script> let para = document.getElementById("para"); console.log(para.style.color); para.style.color = "magenta"; </script>
一些样式属性名包含破折号,好比font-family
。因为这些属性的命名不适合在 JavaScript 中使用(你必须写成style["font-family"]
),所以在 JavaScript 中,样式对象中的属性名都移除了破折号,并将破折号以后的字母大写(style.fontFamily
)。
咱们把 HTML 的样式化系统称为 CSS,即层叠样式表(Cascading Style Sheets)。样式表是一系列规则,指出如何为文档中元素添加样式。能够在<style>
标签中写入 CSS。
<style> strong { font-style: italic; color: gray; } </style> <p>Now <strong>strong text</strong> is italic and gray.</p>
所谓层叠指的是将多条规则组合起来产生元素的最终样式。在示例中,<strong>
标签的默认样式font-weight:bold
,会被<style>
标签中的规则覆盖,并为<strong>
标签样式添加font-style
和color
属性。
当多条规则重复定义同一属性时,最近的规则会拥有最高的优先级。所以若是<style>
标签中的规则包含font-weight:normal
,违背了默认的font-weight
规则,那么文本将会显示为普通样式,而非粗体。属性style
中的样式会直接做用于节点,并且每每拥有最高优先级。
咱们能够在 CSS 规则中使用标签名来定位标签。规则.abc
指的是全部class
属性中包含abc
的元素。规则#xyz
做用于id
属性为xyz
(应当在文档中惟一存在)的元素。
.subtle { color: gray; font-size: 80%; } #header { background: blue; color: white; } /* p elements with id main and with classes a and b */ p#main.a.b { margin-bottom: 20px; }
优先级规则偏向于最近定义的规则,仅在规则特殊性相同时适用。规则的特殊性用于衡量该规则描述匹配元素时的准确性。特殊性取决于规则中的元素数量和类型(tag
、class
或id
)。例如,目标规则p.a
比目标规则p
或.a
更具体,所以有更高优先级。
p>a
这种写法将样式做用于<p>
标签的直系子节点。相似的,p a
应用于全部的<p>
标签中的<a>
标签,不管是不是直系子节点。
本书不会使用太多样式表。尽管理解样式表对浏览器程序设计相当重要,想要正确解释全部浏览器支持的属性及其使用方式,可能须要两到三本书才行。
我介绍选择器语法(用在样式表中,肯定样式做用的元素)的主要缘由是这种微型语言同时也是一种高效的 DOM 元素查找方式。
document
对象和元素节点中都定义了querySelectorAll
方法,该方法接受一个选择器字符串并返回类数组对象,返回的对象中包含全部匹配的元素。
<p>And if you go chasing <span class="animal">rabbits</span></p> <p>And you know you're going to fall</p> <p>Tell 'em a <span class="character">hookah smoking <span class="animal">caterpillar</span></span></p> <p>Has given you the call</p> <script> function count(selector) { return document.querySelectorAll(selector).length; } console.log(count("p")); // All <p> elements // → 4 console.log(count(".animal")); // Class animal // → 2 console.log(count("p .animal")); // Animal inside of <p> // → 2 console.log(count("p > .animal")); // Direct child of <p> // → 1 </script>
与getElementsByTagName
这类方法不一样,由querySelectorAll
返回的对象不是动态变动的。修改文档时其内容不会被修改。但它仍然不是一个真正的数组,因此若是你打算将其看作真的数组,你仍然须要调用Array.from
。
querySelector
方法(没有All
)与querySelectorAll
做用类似。若是只想寻找某一个特殊元素,该方法很是有用。该方法只返回第一个匹配的元素,若是没有匹配的元素则返回null
。
position
样式属性是一种强大的布局方法。默认状况下,该属性值为static
,表示元素处于文档中的默认位置。若该属性设置为relative
,该元素在文档中依然占据空间,但此时其top
和left
样式属性则是相对于常规位置的偏移。若position
设置为absolute
,会将元素从默认文档流中移除,该元素将再也不占据空间,而会与其余元素重叠。其top
和left
属性则是相对其最近的闭合元素的偏移,其中position
属性的值不是static
。若是没有任何闭合元素存在,则是相对于整个文档的偏移。
咱们可使用该属性建立一个动画。下面的文档用于显示一幅猫的图片,该图片会沿着椭圆轨迹移动。
<p style="text-align: center"> <img src="https://gitee.com/wizardforcel/eloquent-js-3e-zh/raw/master/img/cat.png" style="position: relative"> </p> <script> let cat = document.querySelector("img"); let angle = Math.PI / 2; function animate(time, lastTime) { if (lastTime != null) { angle += (time - lastTime) * 0.001; } lastTime = time; cat.style.top = (Math.sin(angle) * 20) + "px"; cat.style.left = (Math.cos(angle) * 200) + "px"; requestAnimationFrame(newTime => animate(newTime, time)); } requestAnimationFrame(animate); </script>
咱们的图像在页面中央,position
为relative
。为了移动这只猫,咱们须要不断更新图像的top
和left
样式。
脚本使用requestAnimationFrame
在每次浏览器准备重绘屏幕时调用animate
函数。animate
函数再次调用requestAnimationFrame
以准备下一次更新。当浏览器窗口(或标签)激活时,更新频率大概为 60 次每秒,这种频率能够生成美观的动画。
若咱们只是在循环中更新 DOM,页面会静止不动,页面上也不会显示任何东西。浏览器不会在执行 JavaScript 程序时刷新显示内容,也不容许页面上的任何交互。这就是咱们须要requestAnimationFrame
的缘由,该函数用于告知浏览器 JavaScript 程序目前已经完成工做,所以浏览器能够继续执行其余任务,好比刷新屏幕,响应用户动做。
咱们将动画生成函数做为参数传递给requestAnimationFrame
。为了确保每一毫秒猫的移动是稳定的,并且动画是圆滑的,它基于一个速度,角度以这个速度改变这一次与上一次函数运行的差。若是仅仅每次走几步,猫的动做可能略显迟钝,例如,另外一个在相同电脑上的繁重任务可能使得该函数零点几秒以后才会运行一次。
咱们使用三角函数Math.cos
和Math.sin
来使猫沿着圆弧移动。你可能不太熟悉这些计算,我在这里简要介绍它们,由于你会在这本书中偶尔遇到。
Math.cos
和Math.sin
很是实用,咱们能够利用一个 1 个弧度,计算出以点(0,0
为圆心的圆上特定点的位置。两个函数都将参数解释为圆上的一个位置,0 表示圆上最右侧那个点,一直逆时针递增到2π
(大概是 6.28),正好走过整个圆。Math.cos
能够计算出圆上某一点对应的x
坐标,而Math.sin
则计算出y
坐标。超过2π
或小于 0 的位置(或角度)都是合法的。由于弧度是循环重复的,a+2π
与a
的角度相同。
用于测量角度的单位称为弧度 - 一个完整的圆弧是2π
个弧度,相似于以角度度量时的 360 度。 常量π
在 JavaScript 中为Math.PI
。
猫的动画代码保存了一个名为angle
的计数器,该绑定记录猫在圆上的角度,并且每当调用animate
函数时,增长该计数器的值。咱们接着使用这个角度来计算图像元素的当前位置。top
样式是Math.sin
的结果乘以 20,表示圆中的垂直弧度。left
样式是 Math.cos 的结果乘以200
,所以圆的宽度大于其高度,致使最后猫会沿着椭圆轨迹移动。
这里须要注意的是样式的值通常须要指定单位。本例中,咱们在数字后添加px
来告知浏览器以像素为计算单位(而非厘米,ems
,或其余单位)。咱们很容易遗漏这个单位。若是咱们没有为样式中的数字加上单位,浏览器最后会忽略掉该样式,除非数字是 0,在这种状况下使用什么单位,其结果都是同样的。
JavaScript 程序能够经过名为 DOM 的数据结构,查看并修改浏览器中显示的文档。该数据结构描述了浏览器文档模型,而 JavaScript 程序能够经过修改该数据结构来修改浏览器展现的文档。
DOM 的组织就像树同样,DOM 根据文档结构来层次化地排布元素。描述元素的对象包含不少属性,好比parentNode
和childNodes
这两个属性能够用来遍历 DOM 树。
咱们能够经过样式来改变文档的显示方式,能够直接在节点上附上样式,也能够编写匹配节点的规则。样式包含许多不一样的属性,好比color
和display
。JavaScript 代码能够直接经过节点的style
属性操做元素的样式。
HTML 表格使用如下标签结构构建:
<table> <tr> <th>name</th> <th>height</th> <th>place</th> </tr> <tr> <td>Kilimanjaro</td> <td>5895</td> <td>Tanzania</td> </tr> </table>
<table>
标签中,每一行包含一个<tr>
标签。<tr>
标签内部则是单元格元素,分为表头(<th>
)和常规单元格(<td>
)。
给定一个山的数据集,一个包含name
,height
和place
属性的对象数组,为枚举对象的表格生成 DOM 结构。 每一个键应该有一列,每一个对象有一行,外加一个顶部带有<th>
元素的标题行,列出列名。
编写这个程序,以便经过获取数据中第一个对象的属性名称,从对象自动产生列。
将所得表格添加到id
属性为"mountains"
的元素,以便它在文档中可见。
当你完成后,将元素的style.textAlign
属性设置为right
,将包含数值的单元格右对齐。
<h1>Mountains</h1> <div id="mountains"></div> <script> const MOUNTAINS = [ {name: "Kilimanjaro", height: 5895, place: "Tanzania"}, {name: "Everest", height: 8848, place: "Nepal"}, {name: "Mount Fuji", height: 3776, place: "Japan"}, {name: "Vaalserberg", height: 323, place: "Netherlands"}, {name: "Denali", height: 6168, place: "United States"}, {name: "Popocatepetl", height: 5465, place: "Mexico"}, {name: "Mont Blanc", height: 4808, place: "Italy/France"} ]; // Your code here </script>
document.getElementsByTagName
方法返回带有特定标签名称的全部子元素。实现该函数,这里注意是函数不是方法。该函数的参数是一个节点和字符串(标签名称),并返回一个数组,该数组包含全部带有特定标签名称的全部后代元素节点。
你可使用nodeName
属性从 DOM 元素中获取标签名称。但这里须要注意,使用tagName
获取的标签名称是全大写形式。可使用字符串的toLowerCase
或toUpperCase
来解决这个问题。
<h1>Heading with a <span>span</span> element.</h1> <p>A paragraph with <span>one</span>, <span>two</span> spans.</p> <script> function byTagName(node, tagName) { // Your code here. } console.log(byTagName(document.body, "h1").length); // → 1 console.log(byTagName(document.body, "span").length); // → 3 let para = document.querySelector("p"); console.log(byTagName(para, "span").length); // → 2 </script>
扩展一下以前定义的用来绘制猫的动画函数,让猫和它的帽子沿着椭圆形轨道边(帽子永远在猫的对面)移动。
你也能够尝试让帽子环绕着猫移动,或修改为其余有趣的动画。
为了便于定位多个对象,一个比较好的方法是使用绝对(absolute
)定位。这就意味着top
和left
属性是相对于文档左上角的坐标。你能够简单地在坐标上加上一个固定数字,以免出现负的坐标,它会使图像移出可见页面。
<style>body { min-height: 200px }</style> <img src="https://gitee.com/wizardforcel/eloquent-js-3e-zh/raw/master/img/cat.png" id="cat" style="position: absolute"> <img src="https://gitee.com/wizardforcel/eloquent-js-3e-zh/raw/master/img/hat.png" id="hat" style="position: absolute"> <script> let cat = document.querySelector("#cat"); let hat = document.querySelector("#hat"); let angle = 0; let lastTime = null; function animate(time) { if (lastTime != null) angle += (time - lastTime) * 0.001; lastTime = time; cat.style.top = (Math.sin(angle) * 40 + 40) + "px"; cat.style.left = (Math.cos(angle) * 200 + 230) + "px"; // Your extensions here. requestAnimationFrame(animate); } requestAnimationFrame(animate); </script>