來源: 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>