JavaScript和DOM

moduzhang發表於2018-09-02
  • 文件物件模型(DOM)
  • 使用 JavaScript 建立內容
  • 使用瀏覽器事件
  • 效能

DOM

我們將深入瞭解文件物件模型 (DOM) 是什麼、如何建立 DOM,以及如何使用 JavaScript 來訪問它。

DOM 代表“文件物件模型”,是一種樹狀結構是HTML 文件的表示,反映了元素之間的關係,幷包含元素的內容和屬性

DOM 不是:

  • JavaScript 語言的一部分

DOM 是:

  • 從瀏覽器構建
  • 可以通過使用 document 物件供 JavaScript 程式碼全域性訪問

如何從 HTML 原始碼轉換為我們在瀏覽器中看到的內容?

這裡寫圖片描述

瀏覽器處理接受資料的規則,整個流程都由令牌解析器來完成,當令牌解析器在執行這一流程時,有另一個流程正在消耗這些令牌(token),並將它們轉換為節點(node)物件。例如,我們轉換了第一個 html 令牌並建立了 html 節點,然後消耗第二個令牌並建立 head 節點。

這裡寫圖片描述

節點之間有關係嗎?是的,注意令牌解析器發出了起始令牌和結束令牌,這樣就可以顯示節點之間的關係。StartTag: head 令牌出現在 EndTag: html 令牌前面,表示 head 令牌是 html 令牌的子級。類似的 meta 和 link 節點是 head 節點的子級,等等,就形成了穩定物件模型(DOM)

這裡寫圖片描述

它是一個樹結構(tree structure),表示 html 的內容和屬性,以及節點之間的關係(子級),注意這些物件包含所有的屬性DOM 是 HTML 標記的完整解析表示

總結上述整個過程步驟:

  • 收到 HTML
  • HTML 標籤被轉換為令牌
  • 令牌被轉換為節點
  • 節點被轉換為 DOM

當你請求一個網站時,無論該網站的後端語言是什麼,它都會用 HTML 進行響應。瀏覽器會接收到一連串的 HTML。這些位元組通過一個複雜的(但完全記錄在案的)解析過程來執行,該過程可以確定不同的字元(例如開始標籤字元 <,像 href 這樣的屬性,像 > 這樣的右尖括號)。解析發生後,接下來是一個稱為標記化的過程。標記化過程每次使用一個字元來構建令牌。這些令牌包括:

  • 文件型別(DOCTYPE)
  • 開始標籤(StartTag)
  • 結束標籤(EndTag)
  • 註釋(Comment)
  • 字元(Character)
  • 檔案結束(End-of-file)

在這個階段,瀏覽器已經收到了伺服器傳送的位元組,並將位元組轉換為標籤,然後讀取了所有標籤,進而建立了一個令牌列表。這個令牌列表將會通過樹構建階段。這個階段的輸出結果是一個樹狀結構——這就是 DOM!

一個樹狀結構,反映了 HTML 的內容和屬性,以及節點之間的所有關係。
DOM 是 HTML 的完整解析表示。

DOM 是所接收到的 HTML 文件的關係和屬性的模型(描述)。請記住,DOM 代表“文件物件模型”,文件的物件模型。可以使用瀏覽器提供的一個特殊物件來訪問 DOM:document,這個物件不是由 JavaScript 語言提供的,但應該已經存在,並且可供 JavaScript 程式碼自由訪問。

DOM 由 W3C 組織進行標準化。有很多組成 DOM 的規範,以下是其中幾個:

  • 核心規範
  • 事件規範
  • 樣式規範
  • 驗證規範
  • 載入和儲存規範

DOM 簡介
W3C 上的 DOM 規範

之前我們學習瞭如何通過 ID(#)、類(.)和標籤(如 p)來選擇頁面元素。接下來,我們將學習如何使用 JavaScript 和 DOM 來訪問頁面元素

通過 ID 選擇頁面元素

document 是一個物件,就像一個 JavaScript 物件一樣,這意味著它有鍵/值對。有些值只是資料片段,而其他值則是可以提供某種功能的函式(也稱為方法!)。

document.getElementById('idName');

我們需要將一個字串傳遞給 .getElementById(),寫明我們希望它查詢並返回給我們的元素的 ID。瞭解更多有關此方法

關於這種方法,有幾件重要的事情需要記住:

  • 它在 document 物件上被呼叫
  • 它會返回單個專案

MDN 上的 .getElementById()

通過類或標籤選擇頁面元素

由於 ID 是唯一的,而且在 HTML 中只有一個元素具有該 ID,因此 document.getElementById() 最多隻會返回一個元素。那麼,我們如何選擇多個 DOM 元素呢?

接下來我們將要介紹的兩個 DOM 方法都會返回多個元素,它們是:

  • .getElementsByClassName()
  • .getElementsByTagName()

關於這兩種方法,有幾件重要的事情需要記住:

  • 兩種方法都使用 document 物件
  • 二者都會返回多個專案
  • 所返回的列表(html集合)不是陣列,特殊的列表,叫做 NodeList

通過類來訪問元素

我們需要使用一個希望讓它搜尋/返回的類字串來呼叫:

document.getElementsByClassName('brand-color');

.getElementById().getElementsByClassName().getElementsByTagName() 二者不同的一點,因為這一點很容易被忽視。那就是,.getElementsByClassName().getElementsByTagName() 的名稱中都多了一個“s”。

通過標籤來訪問元素

document.getElementsByTagName('p');

節點、元素和介面

節點介面

順序 專案
首先發生 標籤(tag)
第二發生 令牌(token)
第三發生 節點(nodes)
第四發生 DOM

DOM 是如何構建的?

  • 字元
  • 標籤
  • 令牌
  • 節點
  • DOM

“節點(Node)”究竟是什麼?

Node = Class // N 大寫表示類
node = object // n 小寫表示物件

這裡寫圖片描述

Node 就像建築的藍圖。該藍圖包含建築資料,我們稱之為屬性。它具有一系列的建築功能,我們稱之為方法。某些屬性可能是建築顏色、門的數量或者窗戶的數量。某些方法可能是保護建築安全的警報系統或者為草澆水的噴灑系統。假設這個藍圖是很多真實建築的模型,替代藍圖的另一個術語是介面。介面會告訴我們應用到單個專案的屬性和方法。

介面用來表示一系列被繼承的屬性和方法

節點(Node)是一個藍圖,包含有關真實節點(nodes)所有屬性和方法的資訊。

MDN 上的節點介面

節點介面是每個真實節點建立後的所有屬性(資料)和方法(功能)的藍圖。節點介面有很多屬性和方法,但它不是很具體。這就好像,“建築藍圖”沒有“房屋藍圖”或“摩天大樓藍圖”那麼具體。後者是更具體的藍圖,而這些更具體的藍圖可能會有自己的屬性和方法,這些屬性和方法只針對房屋或只針對摩天大樓。

元素介面(Element)

正如節點介面一樣,元素介面是用於建立元素的藍圖:MDN 上的元素介面

這裡寫圖片描述

元素指向其父代-節點,即元素介面是節點介面的子代。這也表明元素介面繼承了所有節點介面的屬性和方法。這意味著,從元素介面建立的任何元素(element,小寫“e”!)同時也是節點介面的子代。也就是說,元素(element,小寫“e”!)同時也是節點(node,小寫“n”!)

元素介面有自己的 .getElementsByClassName(),它和 document 物件上的同名方法具有完全相同的功能。這意味著,你可以使用 document 物件來選擇一個元素,然後你可以對該元素呼叫 .getElementsByClassName(),從而接收到一系列帶有該類名的元素,它們都是該特定元素的子代!

// 選擇 ID 為 "sidebar" 的 DOM 元素
const sidebarElement = document.getElementById('sidebar');

// 在 "sidebar" 元素中找尋任何具有 "sub-heading" 類的元素
const subHeadingList = sidebarElement.getElementsByClassName('sub-heading');

要檢視所有不同的介面,請參見:Web API 介面

更多訪問元素方法

幾乎 MDN 上的每個方法都有一個瀏覽器相容性表格,列出了每個瀏覽器開始支援該特定方法的時間。值得慶幸的是,所有瀏覽器基本上都支援官方標準

但在過去,情況並非如此。你必須編寫不同的程式碼,才能在不同的瀏覽器中執行相同的操作。而且,你還得編寫程式碼來檢查所使用的瀏覽器型別,才能為該瀏覽器執行正確的程式碼。幾個 JavaScript 庫應運而生,以幫助緩解這些問題。 jQuery庫 的主要作用之一是抽象畫不同瀏覽器之間的區別。作為開發者,編寫特定的 jQuery 方法,然後 jQuery 會判斷執行的是哪個瀏覽器並針對該瀏覽器使用正確的程式碼。因為jQuery 使我們可以非常輕鬆地編寫可在多個瀏覽器中正確執行的程式碼

querySelector 方法

// 找尋並且返回 ID 名為 "header" 的元素
document.querySelector('#header');

// 找尋並且返回第一個 類 名為 "header" 的元素
document.querySelector('.header');

// 找尋並返回第一個 <header> 元素
document.querySelector('header');

請檢視 MDN 上的 .querySelector() 方法:https://developer.mozilla.org/zh-CN/docs/Web/API/Document/querySelector

注意,我想指出一個潛在的棘手問題,那就是 .querySelector() 方法只會返回一個元素。如果你使用它通過 ID 搜尋元素,這是很合理的。但是,儘管 .getElementsByClassName().getElementsByTagName() 都會返回多個元素的列表,使用 .querySelector() 執行類選擇器或標籤選擇器仍會只返回它所找到的第一個專案。

querySelectorAll 方法

.querySelector() 方法只會返回 DOM 中的一個元素(如果存在的話)。但是,確實有些時候你會想要獲取具有特定類的所有元素列表,或某種型別的所有元素(例如所有 <tr> 標籤)。我們可以使用 .querySelectorAll() 方法來做到這一點!

// 找尋並返回所有 類 名為 "header" 的元素列表
document.querySelectorAll('.header');

// find and return a list of <header> elements
// 找尋並返回所有 <header> 的元素列表
document.querySelectorAll('header');

當獲得所有元素列表(NodeList)後,因為它不是陣列,我們無法使用陣列方法 .map 迴圈訪問列表中的專案。但 NodeList 提供了 forEach 方法和陣列方法 forEach 非常相似,但是並非所有瀏覽器都完全支援 forEach 方法。

如果要迴圈訪問列表中的專案,可以使用 for 迴圈。首先將返回的所有元素儲存在變數中:

const listOfElements = document.querySelectorAll('h2');
for(let I = 0; I < listOfElements.length; I++) {
    console.log(listOfElements[I]);
}

請參見 MDN 上的 .querySelectorAll() 方法:https://developer.mozilla.org/zh-CN/docs/Web/API/Document/querySelectorAll

相關文章