摘要:在其沙箱中提供了將文本轉(zhuǎn)換成文檔對象模型的功能。瀏覽器使用與該形狀對應(yīng)的數(shù)據(jù)結(jié)構(gòu)來表示文檔。我們將這種表示方式稱為文檔對象模型,或簡稱。樹回想一下第章中提到的語法樹。語言的語法樹有標(biāo)識符值和應(yīng)用節(jié)點(diǎn)。元素表示標(biāo)簽的節(jié)點(diǎn)用于確定文檔結(jié)構(gòu)。
來源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目原文:The Document Object Model
譯者:飛龍
協(xié)議:CC BY-NC-SA 4.0
自豪地采用谷歌翻譯
部分參考了《JavaScript 編程精解(第 2 版)》
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》
當(dāng)你在瀏覽器中打開網(wǎng)頁時(shí),瀏覽器會(huì)接收網(wǎng)頁的 HTML 文本并進(jìn)行解析,其解析方式與第 11 章中介紹的解析器非常相似。瀏覽器構(gòu)建文檔結(jié)構(gòu)的模型,并使用該模型在屏幕上繪制頁面。
JavaScript 在其沙箱中提供了將文本轉(zhuǎn)換成文檔對象模型的功能。它是你可以讀取或者修改的數(shù)據(jù)結(jié)構(gòu)。模型是一個(gè)所見即所得的數(shù)據(jù)結(jié)構(gòu),改變模型會(huì)使得屏幕上的頁面產(chǎn)生相應(yīng)變化。
文檔結(jié)構(gòu)你可以將 HTML 文件想象成一系列嵌套的箱子。諸如和之類的標(biāo)簽會(huì)將其他標(biāo)簽包圍起來,而包含在內(nèi)部的標(biāo)簽也可以包含其他的標(biāo)簽和文本。這里給出上一章中已經(jīng)介紹過的示例文件。
My home page My home page
Hello, I am Marijn and this is my home page.
I also wrote a book! Read it here.
該頁面結(jié)構(gòu)如下所示。
瀏覽器使用與該形狀對應(yīng)的數(shù)據(jù)結(jié)構(gòu)來表示文檔。每個(gè)盒子都是一個(gè)對象,我們可以和這些對象交互,找出其中包含的盒子與文本。我們將這種表示方式稱為文檔對象模型(Document Object Model),或簡稱 DOM。
我們可以通過全局綁定document來訪問這些對象。該對象的documentElement屬性引用了標(biāo)簽對象。由于每個(gè) HTML 文檔都有一個(gè)頭部和一個(gè)主體,它還具有head和body屬性,指向這些元素。
樹回想一下第 12 章中提到的語法樹。其結(jié)構(gòu)與瀏覽器文檔的結(jié)構(gòu)極為相似。每個(gè)節(jié)點(diǎn)使用children引用其他節(jié)點(diǎn),而每個(gè)子節(jié)點(diǎn)又有各自的children。其形狀是一種典型的嵌套結(jié)構(gòu),每個(gè)元素可以包含與其自身相似的子元素。
如果一個(gè)數(shù)據(jù)結(jié)構(gòu)有分支結(jié)構(gòu),而且沒有任何環(huán)路(一個(gè)節(jié)點(diǎn)不能直接或間接包含自身),并且有一個(gè)單一、定義明確的“根節(jié)點(diǎn)”,那么我們將這種數(shù)據(jù)結(jié)構(gòu)稱之為樹。就 DOM 來講,document.documentElement就是其根節(jié)點(diǎn)。
在計(jì)算機(jī)科學(xué)中,樹的應(yīng)用極為廣泛。除了表現(xiàn)諸如 HTML 文檔或程序之類的遞歸結(jié)構(gòu),樹還可以用于維持?jǐn)?shù)據(jù)的有序集合,因?yàn)樵跇渲袑ふ一虿迦胍粋€(gè)節(jié)點(diǎn)往往比在數(shù)組中更高效。
一棵典型的樹有不同類型的節(jié)點(diǎn)。Egg 語言的語法樹有標(biāo)識符、值和應(yīng)用節(jié)點(diǎn)。應(yīng)用節(jié)點(diǎn)常常包含子節(jié)點(diǎn),而標(biāo)識符、值則是葉子節(jié)點(diǎn),也就是沒有子節(jié)點(diǎn)的節(jié)點(diǎn)。
DOM中也是一樣。元素(表示 HTML 標(biāo)簽)的節(jié)點(diǎn)用于確定文檔結(jié)構(gòu)。這些節(jié)點(diǎn)可以包含子節(jié)點(diǎn)。這類節(jié)點(diǎn)中的一個(gè)例子是document.body。其中一些子節(jié)點(diǎn)可以是葉子節(jié)點(diǎn),比如文本片段或注釋。
每個(gè) DOM 節(jié)點(diǎn)對象都包含nodeType屬性,該屬性包含一個(gè)標(biāo)識節(jié)點(diǎn)類型的代碼(數(shù)字)。元素的值為 1,DOM 也將該值定義成一個(gè)常量屬性document.ELEMENT_NODE。文本節(jié)點(diǎn)(表示文檔中的一段文本)代碼為 3(document.TEXT_NODE)。注釋的代碼為 8(document.COMMENT_NODE)。
因此我們可以使用另一種方法來表示文檔樹:
葉子節(jié)點(diǎn)是文本節(jié)點(diǎn),而箭頭則指出了節(jié)點(diǎn)之間的父子關(guān)系。
標(biāo)準(zhǔn)并非只有 JavaScript 會(huì)使用數(shù)字代碼來表示節(jié)點(diǎn)類型。本章隨后將會(huì)展示其他的 DOM 接口,你可能會(huì)覺得這些接口有些奇怪。這是因?yàn)?DOM 并不是為 JavaScript 而設(shè)計(jì)的,它嘗試成為一組語言中立的接口,確保也可用于其他系統(tǒng)中,不只是 HTML,還有 XML。XML 是一種通用數(shù)據(jù)格式,語法與 HTML 相近。
這就比較糟糕了。一般情況下標(biāo)準(zhǔn)都是非常易于使用的。但在這里其優(yōu)勢(跨語言的一致性)并不明顯。相較于為不同語言提供類似的接口,如果能夠?qū)⒔涌谂c開發(fā)者使用的語言進(jìn)行適當(dāng)集成,可以為開發(fā)者節(jié)省大量時(shí)間。
我們舉例來說明一下集成問題。比如 DOM 中每個(gè)元素都有childNodes屬性。該屬性是一個(gè)類數(shù)組對象,有length屬性,也可以使用數(shù)字標(biāo)簽訪問對應(yīng)的子節(jié)點(diǎn)。但該屬性是NodeList類型的實(shí)例,而不是真正的數(shù)組,因此該類型沒有諸如slice和map之類的方法。
有些問題是由不好的設(shè)計(jì)導(dǎo)致的。例如,我們無法在創(chuàng)建新的節(jié)點(diǎn)的同時(shí)立即為其添加子節(jié)點(diǎn)和屬性。相反,你首先需要?jiǎng)?chuàng)建節(jié)點(diǎn),然后使用副作用,將子節(jié)點(diǎn)和屬性逐個(gè)添加到節(jié)點(diǎn)中。大量使用 DOM 的代碼通常較長、重復(fù)和丑陋。
但這些問題并非無法改善。因?yàn)?JavaScript 允許我們構(gòu)建自己的抽象,可以設(shè)計(jì)改進(jìn)方式來表達(dá)你正在執(zhí)行的操作。 許多用于瀏覽器編程的庫都附帶這些工具。
沿著樹移動(dòng)DOM 節(jié)點(diǎn)包含了許多指向相鄰節(jié)點(diǎn)的鏈接。下面的圖表展示了這一點(diǎn)。
盡管圖表中每種類型的節(jié)點(diǎn)只顯示出一條鏈接,但每個(gè)節(jié)點(diǎn)都有parentNode屬性,指向一個(gè)節(jié)點(diǎn),它是這個(gè)節(jié)點(diǎn)的一部分。類似的,每個(gè)元素節(jié)點(diǎn)(節(jié)點(diǎn)類型為 1)均包含childNodes屬性,該屬性指向一個(gè)類數(shù)組對象,用于保存其子節(jié)點(diǎn)。
理論上,你可以通過父子之間的鏈接移動(dòng)到樹中的任何地方。但 JavaScript 也提供了一些更加方便的額外鏈接。firstChild屬性和lastChild屬性分別指向第一個(gè)子節(jié)點(diǎn)和最后一個(gè)子節(jié)點(diǎn),若沒有子節(jié)點(diǎn)則值為null。類似的,previousSibling和nextSibling指向相鄰節(jié)點(diǎn),分別指向擁有相同父親的前一個(gè)節(jié)點(diǎn)和后一個(gè)節(jié)點(diǎn)。對于第一個(gè)子節(jié)點(diǎn),previousSibling是null,而最后一個(gè)子節(jié)點(diǎn)的nextSibling則是null。
也存在children屬性,它就像childNodes,但只包含元素(類型為 1)子節(jié)點(diǎn),而不包含其他類型的子節(jié)點(diǎn)。 當(dāng)你對文本節(jié)點(diǎn)不感興趣時(shí),這可能很有用。
處理像這樣的嵌套數(shù)據(jù)結(jié)構(gòu)時(shí),遞歸函數(shù)通常很有用。 以下函數(shù)在文檔中掃描包含給定字符串的文本節(jié)點(diǎn),并在找到一個(gè)時(shí)返回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
因?yàn)?b>childNodes不是真正的數(shù)組,所以我們不能用for/of來遍歷它,并且必須使用普通的for循環(huán)遍歷索引范圍。
文本節(jié)點(diǎn)的nodeValue屬性保存它所表示的文本字符串。
查找元素使用父節(jié)點(diǎn)、子節(jié)點(diǎn)和兄弟節(jié)點(diǎn)之間的連接遍歷節(jié)點(diǎn)確實(shí)非常實(shí)用。但是如果我們只想查找文檔中的特定節(jié)點(diǎn),那么從document.body開始盲目沿著硬編碼的鏈接路徑查找節(jié)點(diǎn)并非良策。如果程序通過樹結(jié)構(gòu)定位節(jié)點(diǎn),就需要依賴于文檔的具體結(jié)構(gòu),而文檔結(jié)構(gòu)隨后可能發(fā)生變化。另一個(gè)復(fù)雜的因素是 DOM 會(huì)為不同節(jié)點(diǎn)之間的空白字符創(chuàng)建對應(yīng)的文本節(jié)點(diǎn)。例如示例文檔中的body標(biāo)簽不止包含 3 個(gè)子節(jié)點(diǎn)(和兩個(gè)
元素),其實(shí)包含 7 個(gè)子節(jié)點(diǎn):這三個(gè)節(jié)點(diǎn)、三個(gè)節(jié)點(diǎn)前后的空格、以及元素之間的空格。
因此,如果你想獲取文檔中某個(gè)鏈接的href屬性,最好不要去獲取文檔body元素中第六個(gè)子節(jié)點(diǎn)的第二個(gè)子節(jié)點(diǎn),而最好直接獲取文檔中的第一個(gè)鏈接,而且這樣的操作確實(shí)可以實(shí)現(xiàn)。
let link = document.body.getElementsByTagName("a")[0]; console.log(link.href);
所有元素節(jié)點(diǎn)都包含getElementsByTagName方法,用于從所有后代節(jié)點(diǎn)中(直接或間接子節(jié)點(diǎn))搜索包含給定標(biāo)簽名的節(jié)點(diǎn),并返回一個(gè)類數(shù)組的對象。
你也可以使用document.getElementById來尋找包含特定id屬性的某個(gè)節(jié)點(diǎn)。
My ostrich Gertrude:
第三個(gè)類似的方法是getElementsByClassName,它與getElementsByTagName類似,會(huì)搜索元素節(jié)點(diǎn)的內(nèi)容并獲取所有包含特定class屬性的元素。
修改文檔幾乎所有 DOM 數(shù)據(jù)結(jié)構(gòu)中的元素都可以被修改。文檔樹的形狀可以通過改變父子關(guān)系來修改。 節(jié)點(diǎn)的remove方法將它們從當(dāng)前父節(jié)點(diǎn)中移除。appendChild方法可以添加子節(jié)點(diǎn),并將其放置在子節(jié)點(diǎn)列表末尾,而insertBefore則將第一個(gè)參數(shù)表示的節(jié)點(diǎn)插入到第二個(gè)參數(shù)表示的節(jié)點(diǎn)前面。
One
Two
Three
每個(gè)節(jié)點(diǎn)只能存在于文檔中的某一個(gè)位置。因此,如果將段落Three插入到段落One前,會(huì)將該節(jié)點(diǎn)從文檔末尾移除并插入到文檔前面,最后結(jié)果為Three/One/Two。所有將節(jié)點(diǎn)插入到某處的方法都有這種副作用——會(huì)將其從當(dāng)前位置移除(如果存在的話)。
replaceChild方法用于將一個(gè)子節(jié)點(diǎn)替換為另一個(gè)子節(jié)點(diǎn)。該方法接受兩個(gè)參數(shù),第一個(gè)參數(shù)是新節(jié)點(diǎn),第二個(gè)參數(shù)是待替換的節(jié)點(diǎn)。待替換的節(jié)點(diǎn)必須是該方法調(diào)用者的子節(jié)點(diǎn)。這里需要注意,replaceChild和insertBefore都將新節(jié)點(diǎn)作為第一個(gè)參數(shù)。
創(chuàng)建節(jié)點(diǎn)假設(shè)我們要編寫一個(gè)腳本,將文檔中的所有圖像(標(biāo)簽)替換為其alt屬性中的文本,該文本指定了圖像的文字替代表示。
這不僅涉及刪除圖像,還涉及添加新的文本節(jié)點(diǎn),并替換原有圖像節(jié)點(diǎn)。為此我們使用document.createTextNode方法。
The
in the
.
給定一個(gè)字符串,createTextNode為我們提供了一個(gè)文本節(jié)點(diǎn),我們可以將它插入到文檔中,來使其顯示在屏幕上。
該循環(huán)從列表末尾開始遍歷圖像。我們必須這樣反向遍歷列表,因?yàn)?b>getElementsByTagName之類的方法返回的節(jié)點(diǎn)列表是動(dòng)態(tài)變化的。該列表會(huì)隨著文檔改變還改變。若我們從列表頭開始遍歷,移除掉第一個(gè)圖像會(huì)導(dǎo)致列表丟失其第一個(gè)元素,第二次循環(huán)時(shí),因?yàn)榧系拈L度此時(shí)為 1,而i也為 1,所以循環(huán)會(huì)停止。
如果你想要獲得一個(gè)固定的節(jié)點(diǎn)集合,可以使用數(shù)組的Array.from方法將其轉(zhuǎn)換成實(shí)際數(shù)組。
let arrayish = {0: "one", 1: "two", length: 2}; let array = Array.from(arrayish); console.log(array.map(s => s.toUpperCase())); // → ["ONE", "TWO"]
你可以使用document.createElement方法創(chuàng)建一個(gè)元素節(jié)點(diǎn)。該方法接受一個(gè)標(biāo)簽名,返回一個(gè)新的空節(jié)點(diǎn),節(jié)點(diǎn)類型由標(biāo)簽名指定。
下面的示例定義了一個(gè)elt工具,用于創(chuàng)建一個(gè)新的元素節(jié)點(diǎn),并將其剩余參數(shù)當(dāng)作該節(jié)點(diǎn)的子節(jié)點(diǎn)。接著使用該函數(shù)為引用添加來源信息。
屬性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.
我們可以通過元素的 DOM 對象的同名屬性去訪問元素的某些屬性,比如鏈接的href屬性。這僅限于最常用的標(biāo)準(zhǔn)屬性。
HTML 允許你在節(jié)點(diǎn)上設(shè)定任何屬性。這一特性非常有用,因?yàn)檫@樣你就可以在文檔中存儲(chǔ)額外信息。你自己創(chuàng)建的屬性不會(huì)出現(xiàn)在元素節(jié)點(diǎn)的屬性中。你必須使用getAttribute和setAttribute方法來訪問這些屬性。
The launch code is 00000000.
I have two feet.
建議為這些組合屬性的名稱添加data-前綴,來確保它們不與任何其他屬性發(fā)生沖突。
這里有一個(gè)常用的屬性:class。該屬性是 JavaScript 中的保留字。因?yàn)槟承v史原因(某些舊版本的 JavaScript 實(shí)現(xiàn)無法處理和關(guān)鍵字或保留字同名的屬性),訪問class的屬性名為className。你也可以使用getAttribute和setAttribute方法,使用其實(shí)際名稱class來訪問該屬性。
布局你可能已經(jīng)注意到不同類型的元素有不同的布局。某些元素,比如段落(
)和標(biāo)題()會(huì)占據(jù)整個(gè)文檔的寬度,并且在獨(dú)立的一行中渲染。這些元素被稱為塊(Block)元素。其他的元素,比如鏈接(或元素則與周圍文本在同一行中渲染。這類元素我們稱之為內(nèi)聯(lián)(Inline)元素。
對于任意特定文檔,瀏覽器可以根據(jù)每個(gè)元素的類型和內(nèi)容計(jì)算其尺寸與位置等布局信息。接著使用布局來繪制文檔。
JavaScript 中可以訪問元素的尺寸與位置。
屬性offsetWidth和offsetHeight給出元素的起始位置(單位是像素)。像素是瀏覽器中的基本測量單元。它通常對應(yīng)于屏幕可以繪制的最小的點(diǎn),但是在現(xiàn)代顯示器上,可以繪制非常小的點(diǎn),這可能不再適用了,并且瀏覽器像素可能跨越多個(gè)顯示點(diǎn)。
同樣,clientWidth和clientHeight向你提供元素內(nèi)的空間大小,忽略邊框?qū)挾取?/p>
I"m boxed in
getBoundingClientRect方法是獲取屏幕中某個(gè)元素精確位置的最有效方法。該方法返回一個(gè)對象,包含top、bottom、left和right四個(gè)屬性,表示元素相對于屏幕左上角的位置(單位是像素)。若你想要知道其相對于整個(gè)文檔的位置,必須加上其滾動(dòng)位置,你可以在pageXOffset和pageYOffset綁定中找到。
我們還需要花些力氣才能完成文檔的排版工作。為了加快速度,每次你改變它時(shí),瀏覽器引擎不會(huì)立即重新繪制整個(gè)文檔,而是盡可能等待并推遲重繪操作。當(dāng)一個(gè)修改文檔的 JavaScript 程序結(jié)束時(shí),瀏覽器會(huì)計(jì)算新的布局,并在屏幕上顯示修改過的文檔。若程序通過讀取offsetHeight和getBoundingClientRect這類屬性獲取某些元素的位置或尺寸時(shí),為了提供正確的信息,瀏覽器也需要計(jì)算布局。
如果程序反復(fù)讀取 DOM 布局信息或修改 DOM,會(huì)強(qiáng)制引發(fā)大量布局計(jì)算,導(dǎo)致運(yùn)行非常緩慢。下面的代碼展示了一個(gè)示例。該示例包含兩個(gè)不同的程序,使用X字符構(gòu)建一條線,其長度是 2000 像素,并計(jì)算每個(gè)任務(wù)的時(shí)間。
樣式
我們看到了不同的 HTML 元素的繪制是不同的。一些元素顯示為塊,一些則是以內(nèi)聯(lián)方式顯示。我們還可以添加一些樣式,比如使用加粗內(nèi)容,或使用使內(nèi)容變成藍(lán)色,并添加下劃線。
標(biāo)簽顯示圖片的方式或點(diǎn)擊標(biāo)簽時(shí)跳轉(zhuǎn)的鏈接都和元素類型緊密相關(guān)。但元素的默認(rèn)樣式,比如文本的顏色、是否有下劃線,都是可以改變的。這里給出使用style屬性的示例。
樣式屬性可以包含一個(gè)或多個(gè)聲明,格式為屬性(比如color)后跟著一個(gè)冒號和一個(gè)值(比如green)。當(dāng)包含更多聲明時(shí),不同屬性之間必須使用分號分隔,比如color:red;border:none。
文檔的很多方面會(huì)受到樣式的影響。例如,display屬性控制一個(gè)元素是否顯示為塊元素或內(nèi)聯(lián)元素。
This text is displayed inline, as a block, and not at all.
block標(biāo)簽會(huì)結(jié)束其所在的那一行,因?yàn)閴K元素是不會(huì)和周圍文本內(nèi)聯(lián)顯示的。最后一個(gè)標(biāo)簽完全不會(huì)顯示出來,因?yàn)?b>display:none會(huì)阻止一個(gè)元素呈現(xiàn)在屏幕上。這是隱藏元素的一種方式。更好的方式是將其從文檔中完全移除,因?yàn)樯院髮⑵浞呕厝ナ且患芎唵蔚氖虑椤?/p>
JavaScript 代碼可以通過元素的style屬性操作元素的樣式。該屬性保存了一個(gè)對象,對象中存儲(chǔ)了所有可能的樣式屬性,這些屬性的值是字符串,我們可以把字符串寫入屬性,修改某些方面的元素樣式。
Nice text
一些樣式屬性名包含破折號,比如font-family。由于這些屬性的命名不適合在 JavaScript 中使用(你必須寫成style["font-family"]),因此在 JavaScript 中,樣式對象中的屬性名都移除了破折號,并將破折號之后的字母大寫(style.fontFamily)。
層疊樣式我們把 HTML 的樣式化系統(tǒng)稱為 CSS,即層疊樣式表(Cascading Style Sheets)。樣式表是一系列規(guī)則,指出如何為文檔中元素添加樣式。可以在標(biāo)簽中寫入 CSS。
Now strong text is italic and gray.
所謂層疊指的是將多條規(guī)則組合起來產(chǎn)生元素的最終樣式。在示例中,標(biāo)簽的默認(rèn)樣式font-weight:bold,會(huì)被標(biāo)簽中的規(guī)則覆蓋,并為標(biāo)簽樣式添加font-style和color屬性。
當(dāng)多條規(guī)則重復(fù)定義同一屬性時(shí),最近的規(guī)則會(huì)擁有最高的優(yōu)先級。因此如果標(biāo)簽中的規(guī)則包含font-weight:normal,違背了默認(rèn)的font-weight規(guī)則,那么文本將會(huì)顯示為普通樣式,而非粗體。屬性style中的樣式會(huì)直接作用于節(jié)點(diǎn),而且往往擁有最高優(yōu)先級。
我們可以在 CSS 規(guī)則中使用標(biāo)簽名來定位標(biāo)簽。規(guī)則.abc指的是所有class屬性中包含abc的元素。規(guī)則#xyz作用于id屬性為xyz(應(yīng)當(dāng)在文檔中唯一存在)的元素。
.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; }
優(yōu)先級規(guī)則偏向于最近定義的規(guī)則,僅在規(guī)則特殊性相同時(shí)適用。規(guī)則的特殊性用于衡量該規(guī)則描述匹配元素時(shí)的準(zhǔn)確性。特殊性取決于規(guī)則中的元素?cái)?shù)量和類型(tag、class或id)。例如,目標(biāo)規(guī)則p.a比目標(biāo)規(guī)則p或.a更具體,因此有更高優(yōu)先級。
p>a這種寫法將樣式作用于
標(biāo)簽的直系子節(jié)點(diǎn)。類似的,p a應(yīng)用于所有的
標(biāo)簽中的標(biāo)簽,無論是否是直系子節(jié)點(diǎn)。
查詢選擇器本書不會(huì)使用太多樣式表。盡管理解樣式表對瀏覽器程序設(shè)計(jì)至關(guān)重要,想要正確解釋所有瀏覽器支持的屬性及其使用方式,可能需要兩到三本書才行。
我介紹選擇器語法(用在樣式表中,確定樣式作用的元素)的主要原因是這種微型語言同時(shí)也是一種高效的 DOM 元素查找方式。
document對象和元素節(jié)點(diǎn)中都定義了querySelectorAll方法,該方法接受一個(gè)選擇器字符串并返回類數(shù)組對象,返回的對象中包含所有匹配的元素。
And if you go chasing rabbits
And you know you"re going to fall
Tell "em a hookah smoking caterpillar
Has given you the call
與getElementsByTagName這類方法不同,由querySelectorAll返回的對象不是動(dòng)態(tài)變更的。修改文檔時(shí)其內(nèi)容不會(huì)被修改。但它仍然不是一個(gè)真正的數(shù)組,所以如果你打算將其看做真的數(shù)組,你仍然需要調(diào)用Array.from。
querySelector方法(沒有All)與querySelectorAll作用相似。如果只想尋找某一個(gè)特殊元素,該方法非常有用。該方法只返回第一個(gè)匹配的元素,如果沒有匹配的元素則返回null。
位置與動(dòng)畫position樣式屬性是一種強(qiáng)大的布局方法。默認(rèn)情況下,該屬性值為static,表示元素處于文檔中的默認(rèn)位置。若該屬性設(shè)置為relative,該元素在文檔中依然占據(jù)空間,但此時(shí)其top和left樣式屬性則是相對于常規(guī)位置的偏移。若position設(shè)置為absolute,會(huì)將元素從默認(rèn)文檔流中移除,該元素將不再占據(jù)空間,而會(huì)與其他元素重疊。其top和left屬性則是相對其最近的閉合元素的偏移,其中position屬性的值不是static。如果沒有任何閉合元素存在,則是相對于整個(gè)文檔的偏移。
我們可以使用該屬性創(chuàng)建一個(gè)動(dòng)畫。下面的文檔用于顯示一幅貓的圖片,該圖片會(huì)沿著橢圓軌跡移動(dòng)。
我們的圖像在頁面中央,position為relative。為了移動(dòng)這只貓,我們需要不斷更新圖像的top和left樣式。
腳本使用requestAnimationFrame在每次瀏覽器準(zhǔn)備重繪屏幕時(shí)調(diào)用animate函數(shù)。animate函數(shù)再次調(diào)用requestAnimationFrame以準(zhǔn)備下一次更新。當(dāng)瀏覽器窗口(或標(biāo)簽)激活時(shí),更新頻率大概為 60 次每秒,這種頻率可以生成美觀的動(dòng)畫。
若我們只是在循環(huán)中更新 DOM,頁面會(huì)靜止不動(dòng),頁面上也不會(huì)顯示任何東西。瀏覽器不會(huì)在執(zhí)行 JavaScript 程序時(shí)刷新顯示內(nèi)容,也不允許頁面上的任何交互。這就是我們需要requestAnimationFrame的原因,該函數(shù)用于告知瀏覽器 JavaScript 程序目前已經(jīng)完成工作,因此瀏覽器可以繼續(xù)執(zhí)行其他任務(wù),比如刷新屏幕,響應(yīng)用戶動(dòng)作。
我們將動(dòng)畫生成函數(shù)作為參數(shù)傳遞給requestAnimationFrame。為了確保每一毫秒貓的移動(dòng)是穩(wěn)定的,而且動(dòng)畫是圓滑的,它基于一個(gè)速度,角度以這個(gè)速度改變這一次與上一次函數(shù)運(yùn)行的差。如果僅僅每次走幾步,貓的動(dòng)作可能略顯遲鈍,例如,另一個(gè)在相同電腦上的繁重任務(wù)可能使得該函數(shù)零點(diǎn)幾秒之后才會(huì)運(yùn)行一次。
我們使用三角函數(shù)Math.cos和Math.sin來使貓沿著圓弧移動(dòng)。你可能不太熟悉這些計(jì)算,我在這里簡要介紹它們,因?yàn)槟銜?huì)在這本書中偶爾遇到。
Math.cos和Math.sin非常實(shí)用,我們可以利用一個(gè) 1 個(gè)弧度,計(jì)算出以點(diǎn)(0,0為圓心的圓上特定點(diǎn)的位置。兩個(gè)函數(shù)都將參數(shù)解釋為圓上的一個(gè)位置,0 表示圓上最右側(cè)那個(gè)點(diǎn),一直逆時(shí)針遞增到2π(大概是 6.28),正好走過整個(gè)圓。Math.cos可以計(jì)算出圓上某一點(diǎn)對應(yīng)的x坐標(biāo),而Math.sin則計(jì)算出y坐標(biāo)。超過2π或小于 0 的位置(或角度)都是合法的。因?yàn)榛《仁茄h(huán)重復(fù)的,a+2π與a的角度相同。
用于測量角度的單位稱為弧度 - 一個(gè)完整的圓弧是2π個(gè)弧度,類似于以角度度量時(shí)的 360 度。 常量π在 JavaScript 中為Math.PI。
貓的動(dòng)畫代碼保存了一個(gè)名為angle的計(jì)數(shù)器,該綁定記錄貓?jiān)趫A上的角度,而且每當(dāng)調(diào)用animate函數(shù)時(shí),增加該計(jì)數(shù)器的值。我們接著使用這個(gè)角度來計(jì)算圖像元素的當(dāng)前位置。top樣式是Math.sin的結(jié)果乘以 20,表示圓中的垂直弧度。left樣式是 Math.cos 的結(jié)果乘以200,因此圓的寬度大于其高度,導(dǎo)致最后貓會(huì)沿著橢圓軌跡移動(dòng)。
這里需要注意的是樣式的值一般需要指定單位。本例中,我們在數(shù)字后添加px來告知瀏覽器以像素為計(jì)算單位(而非厘米,ems,或其他單位)。我們很容易遺漏這個(gè)單位。如果我們沒有為樣式中的數(shù)字加上單位,瀏覽器最后會(huì)忽略掉該樣式,除非數(shù)字是 0,在這種情況下使用什么單位,其結(jié)果都是一樣的。
本章小結(jié)JavaScript 程序可以通過名為 DOM 的數(shù)據(jù)結(jié)構(gòu),查看并修改瀏覽器中顯示的文檔。該數(shù)據(jù)結(jié)構(gòu)描述了瀏覽器文檔模型,而 JavaScript 程序可以通過修改該數(shù)據(jù)結(jié)構(gòu)來修改瀏覽器展示的文檔。
DOM 的組織就像樹一樣,DOM 根據(jù)文檔結(jié)構(gòu)來層次化地排布元素。描述元素的對象包含很多屬性,比如parentNode和childNodes這兩個(gè)屬性可以用來遍歷 DOM 樹。
我們可以通過樣式來改變文檔的顯示方式,可以直接在節(jié)點(diǎn)上附上樣式,也可以編寫匹配節(jié)點(diǎn)的規(guī)則。樣式包含許多不同的屬性,比如color和display。JavaScript 代碼可以直接通過節(jié)點(diǎn)的style屬性操作元素的樣式。
習(xí)題 創(chuàng)建一張表HTML 表格使用以下標(biāo)簽結(jié)構(gòu)構(gòu)建:
name | height | place |
---|---|---|
Kilimanjaro | 5895 | Tanzania |
給定一個(gè)山的數(shù)據(jù)集,一個(gè)包含name,height和place屬性的對象數(shù)組,為枚舉對象的表格生成 DOM 結(jié)構(gòu)。 每個(gè)鍵應(yīng)該有一列,每個(gè)對象有一行,外加一個(gè)頂部帶有 編寫這個(gè)程序,以便通過獲取數(shù)據(jù)中第一個(gè)對象的屬性名稱,從對象自動(dòng)產(chǎn)生列。 將所得表格添加到id屬性為"mountains"的元素,以便它在文檔中可見。 當(dāng)你完成后,將元素的style.textAlign屬性設(shè)置為right,將包含數(shù)值的單元格右對齊。 document.getElementsByTagName方法返回帶有特定標(biāo)簽名稱的所有子元素。實(shí)現(xiàn)該函數(shù),這里注意是函數(shù)不是方法。該函數(shù)的參數(shù)是一個(gè)節(jié)點(diǎn)和字符串(標(biāo)簽名稱),并返回一個(gè)數(shù)組,該數(shù)組包含所有帶有特定標(biāo)簽名稱的所有后代元素節(jié)點(diǎn)。 你可以使用nodeName屬性從 DOM 元素中獲取標(biāo)簽名稱。但這里需要注意,使用tagName獲取的標(biāo)簽名稱是全大寫形式??梢允褂米址?b>toLowerCase或toUpperCase來解決這個(gè)問題。 A paragraph with one, two
spans. 擴(kuò)展一下之前定義的用來繪制貓的動(dòng)畫函數(shù),讓貓和它的帽子沿著橢圓形軌道邊(帽子永遠(yuǎn)在貓的對面)移動(dòng)。 你也可以嘗試讓帽子環(huán)繞著貓移動(dòng),或修改成其他有趣的動(dòng)畫。 為了便于定位多個(gè)對象,一個(gè)比較好的方法是使用絕對(absolute)定位。這就意味著top和left屬性是相對于文檔左上角的坐標(biāo)。你可以簡單地在坐標(biāo)上加上一個(gè)固定數(shù)字,以避免出現(xiàn)負(fù)的坐標(biāo),它會(huì)使圖像移出可見頁面。 文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。 轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/105049.html 摘要:在本例中,使用屬性指定鏈接的目標(biāo),其中表示超文本鏈接。您應(yīng)該認(rèn)為和元數(shù)據(jù)隱式出現(xiàn)在示例中,即使它們沒有實(shí)際顯示在文本中。
來源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目原文:JavaScript and the Browser
譯者:飛龍
協(xié)議:CC BY-NC-SA 4.0
自豪地采用谷歌翻譯
部分參考了《JavaScript 編程精解(第 2 版)》
... 摘要:事件與節(jié)點(diǎn)每個(gè)瀏覽器事件處理器被注冊在上下文中。事件對象雖然目前為止我們忽略了它,事件處理器函數(shù)作為對象傳遞事件對象。若事件處理器不希望執(zhí)行默認(rèn)行為通常是因?yàn)橐呀?jīng)處理了該事件,會(huì)調(diào)用事件對象的方法。
來源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目原文:Handling Events
譯者:飛龍
協(xié)議:CC BY-NC-SA 4.0
自豪地采用谷歌翻譯
部分... 摘要:來源編程精解中文第三版翻譯項(xiàng)目原文譯者飛龍協(xié)議自豪地采用谷歌翻譯部分參考了編程精解第版,這是一本關(guān)于指導(dǎo)電腦的書。在可控的范圍內(nèi)編寫程序是編程過程中首要解決的問題。我們可以用中文來描述這些指令將數(shù)字存儲(chǔ)在內(nèi)存地址中的位置。
來源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目原文:Introduction
譯者:飛龍
協(xié)議:CC BY-NC-SA 4.0
自豪地... 摘要:來源編程精解中文第三版翻譯項(xiàng)目原文譯者飛龍協(xié)議自豪地采用谷歌翻譯置疑計(jì)算機(jī)能不能思考就相當(dāng)于置疑潛艇能不能游泳。這張圖將成為我們的機(jī)器人在其中移動(dòng)的世界。機(jī)器人在收到包裹時(shí)拾取包裹,并在抵達(dá)目的地時(shí)將其送達(dá)。這個(gè)機(jī)器人已經(jīng)快了很多。
來源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目原文:Project: A Robot
譯者:飛龍
協(xié)議:CC BY-NC-S... 摘要:來源編程精解中文第三版翻譯項(xiàng)目原文譯者飛龍協(xié)議自豪地采用谷歌翻譯編寫易于刪除,而不是易于擴(kuò)展的代碼。模塊之間的關(guān)系稱為依賴關(guān)系。用于連接模塊的最廣泛的方法稱為模塊。模塊的主要概念是稱為的函數(shù)。
來源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目原文:Modules
譯者:飛龍
協(xié)議:CC BY-NC-SA 4.0
自豪地采用谷歌翻譯
編寫易于刪除,而不是易于擴(kuò)... 閱讀 2264·2021-09-23 11:52 閱讀 1929·2021-09-02 15:41 閱讀 3053·2019-08-30 10:47 閱讀 2015·2019-08-29 17:14 閱讀 2373·2019-08-29 16:16 閱讀 3217·2019-08-28 18:29 閱讀 3452·2019-08-26 13:30 閱讀 2635·2019-08-26 10:49標(biāo)簽中,每一行包含一個(gè)
標(biāo)簽。 標(biāo)簽內(nèi)部則是單元格元素,分為表頭( )和常規(guī)單元格( )。
元素的標(biāo)題行,列出列名。
通過標(biāo)簽名獲取元素
Mountains
貓的帽子
Heading with a span element.
相關(guān)文章
JavaScript 編程精解 中文第三版 十三、瀏覽器中的 JavaScript
JavaScript 編程精解 中文第三版 十五、處理事件
JavaScript 編程精解 中文第三版 零、前言
JavaScript 編程精解 中文第三版 七、項(xiàng)目:機(jī)器人
JavaScript 編程精解 中文第三版 十、模塊
發(fā)表評論
0條評論
gggggggbong
男|高級講師
TA的文章
閱讀更多
鯨魚數(shù)據(jù):江西CN2,1核/512MB內(nèi)存/5GB SSD空間/1TB流量/100Mbps-230M
一 iOS 開發(fā)因“玩手機(jī)”被開除,段子照進(jìn)現(xiàn)實(shí)了......
一起來了解下這些webpack常用插件
【每日一包0006】dedupe
html&&css
前端編碼規(guī)范
原生 js 實(shí)現(xiàn)一個(gè)前端路由 router
angular(angular6/angular7/angular8) delete請求body的問