DOM from the begineeing

DOM API 一篇就會!

為什麼需要學 DOM API?

當網頁加載 HTML 文件時瀏覽器會生成 DOM,而開發者們可以藉由 DOM API 讓開發者可以程序化的操縱整個頁面文件。由於 JavaScript 並不了解 HTML 的架構,因此瀏覽器生成出來的 DOM 就是 HTML 但用「物件」的方式作呈現。 簡單來說,學習 DOM API 可以幫助我們使用 JavaScript 操縱網頁上任何元素

如果將 DOM 轉換為圖表理解,會以類似「樹狀關係圖」的樣貌展現,因此 DOM Tree 也代表 DOM 架構資料的圖像化呈現,如下圖:

DOM 的樹狀圖表

什麼是 DOM 節點?

名稱說明
文件節點代表整個文件的起點,所有節點的起源
元素節點代表元素(Element),像是 <div><script><h1>
屬性節點代表元素的屬性如 idclass
文字節點代表元素或屬性內的純文字內容,換行與空格也是一種文字節點。
註解節點代表文件中的註解

任何東西在 HTML 文件中都是一種 DOM 節點,常見的節點可參考以上圖表。節點是構成 DOM 的最基礎的材料,了解有不同的節點可以更清楚的知道想選取的內容、選到的內容是什麼、應該如何處理。在文章後頭會認識到各種方法來選取這些不同種類的節點,進而程序化的操縱與監聽它們來達成程序化控制網頁的目的。

接下來將會分為三大章節,一步一步學習如何運用 JavaScript 透過 DOM API 操作網頁,學會存取、編輯與監聽 DOM

一、選取 DOM 節點

HTML 的元素之間有輩份關係,這點也體現在 DOM 的結構上,可以依據一個節點來抓取不同關係的節點,舉以下的代碼為例:

<div>
<h2>A List</h2>
<ul>
<li>List Item 01</li>
<li>List Item 02</li>
</ul>
</div>

<ul> 這個元素來說: <h2> 就是其同輩 <div> 就是其父母 <li> 就是其子孫。了解元素彼此間的關係之後,就可以運用不同的方法來選取網頁中的節點。

實際常見範例

在大多情境下其實不用記下太多的選取方法,只要記得常用的即可,像是選取單一元素的 qerySelector🔗 以及選取多個元素的 querySelectorAll🔗 就可以應付絕大多數約 90% 的情境。

<div id="foobar" class="foobar">Hello, World</div>
<ul>
<li>Item 01</li>
<li>Item 02</li>
<li>Item 03</li>
</ul>
<script>
// 依 document 為基準,選取符合 .foobar 選擇器群組的元素
const foobar = document.querySelector('.foobar');
// 依 document 為基準,選取所有 ul 內的 li
const foobar2 = document.querySelectorAll('ul li');
</script>

更進階的選取 DOM 節點

練習完不同選取 DOM 的方法之後,會發現不同的方法返回的結果會不太一樣,這是因為它們所選取的「東西」不太相同,總共會遇上四種返回的結果。細節綜合成以下清單與表單:

節點

構成 DOM 的基礎組成,任何 DOM 中的東西都是結點。

元素

元素是一種節點,舉例來說 <h1><body><div> 都是元素。

節點清單

節點的集合,一筆類似陣列的資料,像是有索引和 forEach 方法。絕大多數時候是靜態的,意味著 DOM 的改變並不會反映於資料上。

HTML 合集

元素的集合,一筆類似陣列的資料,像是有索引。是動態的集合,意味著 DOM 的改變都會反映於資料上。

名稱元素 Element節點 Node
內容只允許 HTML 元素(像是 <span> <div>之類的標籤)只允許任何存在於 HTML 文件中的節點(註解、內文字都算)
方法與屬性一種特別的節點,擁有一些額外的屬性最基礎的 HTML 元件,一些基礎的屬性
使用時機絕大多數時候選取 HTML 標籤以外的事物時
名稱HTML 合集 HTMLCollection節點清單 NodeList
說明文件元素的合集文件節點的合集
可使用的陣列方法僅有 forEach
即時更新大多不是

二、編輯 DOM 節點

當選取完 DOM 節點後讓將結果打印出來,看看有哪些屬性:

<div id="foobar">Hello</div>
<script>
// 依 document 為基準,選取第一個 id 為 foobar 的元素並打印出來
console.log(document.getElementById('foobar'));
</script>

得到類似以下動圖中的打印結果:

可以發現每個元素物件身上都有許多的屬性與方法可操作,看起來非常的複雜,但常用的屬性實際上並不多,在需要的時候再查閱文檔了解即可。以下用兩種最常被選取的屬性種類作為範例來介紹(改樣式與內容):

改變元素外表樣式

要修改元素的外觀樣式可以從 class 屬性與 style 屬性下手,可以直接修改元素屬性來達成更改樣式的目的,舉以下例子來說:

// 修改元素的 style 屬性:
element.style.backgroundColor = 'red';
element.style.marginTop = '16px';
element.style['padding-top'] = '16px';
// 修改元素的 style 屬性:
element.classList.add('active'); // 新增 "active" class
element.classList.remove('active'); // 移除 "active" class
element.classList.toggle('active'); // 切換 "active" class

改變元素內容

要修改元素的內容常見可以分為「修改 HTML」與「修改純文字」,端看需求選擇不同的方式,舉以下例子來說:

// 修改選取元素的 HTML 內容
document.getElementById('foobar').innerHTML = '<h1>This is some text</h1>';
// 修改選取元素的文字內容
document.getElementById('foobar').innerText = 'This is some text';

三、監聽 DOM 節點

addEventListener 事件監聽器

如果想要知道用戶對網頁作了那些互動,就可以使用 addEventListener🔗 方法來監聽元素的活動,大多時候範例一、二就可以應對所有事件監聽的要求。語法如下:

addEventListener(type, listener, options);
  • 參數一:事件的種類🔗
  • 參數二:回呼函數,當事件被觸發時就會被執行
  • 參數三:指定有關事件偵聽器的選項🔗
// 使用元素的 addEventListener 方法,當 "click" 時呼叫 clicked 函式
// 以下三種寫法都可行
// 範例一:呼叫外部函式
element.addEventListener('click', clicked);
function clicked() {
// ...
}
// 範例二:呼叫匿名函式
element.addEventListener('click', function () {
// ...
});
// 範例三:添加設定物件:當激活過一次這個事件監聽就會停止監聽
element.addEventListener('click', function () {}, { once: true });

removeEventListener 移除事件監聽器

當不需要事件監聽時可以手動刪除來避免背景有太多事件監聽導致影響效能,這項方法僅能刪除以呼叫外部函式來執行的事件監聽器,呼叫匿名函式的事件監聽器是刪不掉的。

// 添加監聽
element.addEventListener('mousemove', myFunction);
// 移除監聽
element.removeEventListener('mousemove', myFunction);

監聽表單

當想要用戶輸入資料,就是 <form> 表單登場的地方,它提供了一系列基礎的方法和介面讓我們可以更方便快速且標準化的獲取用戶輸入的資料,其中可使用 submit 事件監聽獲取提交的表單。

<form data-form>
<input name="userInput" type="text"> // 設定 name 以便之後讀取
<input type="submit">
</form>
const form = document.querySelector('[data-form]');
// 帶入
form.addEventListener('submit', (e) => {
console.log(e.target.userInput.value); // 獲取 name = "userInput" 的值
e.preventDefault(); // 取消預設事件(跳轉頁面)
e.target.reset(); // 清空表單
});

阻擋預設行為

在某些元素上會自帶預設的「事件」,像是點擊 <a> 或提交 <form> 都會跳轉到其他頁面,這時候就可以使用 preventDefault 來取消這些預設事件。此外在事件監聽最尾端傳入一個 false 也有同樣阻擋預設行為的效果。

element.addEventListener('click', (e) => {
e.preventDefault(); // 獲取事件,阻擋預設行為發生
});

總結

  • DOM 是文件使用物件的方式做呈現,畫成圖表架構就像一棵樹。
  • HTML 文件中的任何資訊在 DOM 中都是一種節點。
  • 選取多個節點會回傳 NodeList ,選取多個元素會回傳 HTMLCollection,它們是類陣列的物件。
  • 節點有許多預設方法與屬性,可以在終端打印出來後查詢文檔即可。

本篇是我在 六角學院🔗 JS 直播班針對該主題所出的入門題目,內含解答,有興趣可以點開來練習:JS 直播班 - 2022 秋季班 - DOM 操作🔗

參考資料