DOM這個問題困擾我很久,看線上課程、文章、影片好像能懂,但要轉化成簡潔的文字說出來又有中文障礙.總是有模糊不清的地方,不是太簡化就是太抽象。後來發現用一些簡短的問題,去理解細部差異似乎還不錯。
開始了解DOM之前,先介紹javascript物件
Javascript『物件』是由許多屬性構成的,假設有個物件叫咪咪,咪咪的屬性包含花色、體型、年齡(註:咪咪是貓,但本篇不提類別跟構造函數),用Javascript 寫出來大概長這樣:
let mimi = {
color: "orange",
shape: "fat",
age: 10,
};
同時又有個物件叫mobuyashea,他是人類,也有自己的屬性,例如名字跟性別:
let mobuyashea = {
name: "鴨血",
gender: "female",
};
let mimi = {
color: "orange",
shape: "fat",
age: 10,
};
咪咪與mobuyashea這兩個看似毫不相關的物件,可以藉由屬性產生關係,這是因為『一個物件可以是其他物件的屬性』。
let mobuyashea = {
name: "鴨血",
gender: "female",
};
let mimi = {
color: "orange",
shape: "fat",
age: 10,
owner: mobuyashea, //加上主人屬性
};
console.log(mimi.owner.name); //鴨血
在咪咪物件中加上owner屬性,其值為mobuyashea,就可以透過咪咪來取得主人的資料。
一、DOM是什麼?
DOM的全名是Document Object Model,中文為文件物件模型,是W3C定義的HTML文件存取標準。其目的是讓各個瀏覽器不分語言平台,有一個共同的核心原則去訪問、操作HTML網頁文件。
在DOM的定義中,一個網頁文件,是由許多javascript『物件』(或稱『節點』)組成,並形成一棵物件樹,如下圖所示:
既然是Javascript物件,Javascript就可以透過這個模型,去操作這些網頁中的物件,又稱DOM操作。以下是Javascript可以操作的事:
- 改變CSS樣式
- 改變、移除、新增HTML標籤或屬性
- 新增HTML事件
- 對現有HTML事件做出反應
二、DOM的物件/節點有哪些?
以下節點類型都可視為物件:
節點類型 | 舉例 |
Document(文件) | 整個HTML網頁 |
Element(元素) | <html>、<head>、<body>、<h1>、<a>等標籤 |
Attribute(屬性) | href、src、class等屬性,<a href=”#”>點我前往</a>當中的href就是屬性節點 |
Text(文字) | <a href=”#”>點我前往</a>的『點我前往』就是文字節點 |
comment(註解) | <!–這就是註解–> |
這樣看下來,似乎可以解讀為除了屬性的值以外,大部分在HTML中看到的東西都可被視為物件,大物件包裹著相同or不同類型的小物件。例如:<head>包裹著同樣是標籤的<title>,而<title>中又包裹著代表網站標題的文字物件。
三、節點關係
3-1.為什麼要釐清節點之間的關係?
因為在執行DOM操作前,不免會經歷DOM遍歷的過程:找到目標物件,並把它(們)選取起來再進行操作,而節點有一些自帶屬性,工程師理解相對關係後更方便操作。
節點之間根據階層,主要分為『父子』、『兄弟』關係,以下面這段HTML為例:
<div id="wrap">
<div id="box">
<a href="#">1</a>
<a href="#">2</a>
<a href="#">3</a>
</div>
</div>
- <a>是<div id=”box”>的子節點
- <div id=”wrap”>是<div id=”box”>的父節點
- 每個<a>之間是兄弟關係
- <a href=”#”>與裡面的Text節點是父子關係
原因在於實務上,如果要選取文字節點或屬性節點,它們有自己對應的屬性方法,例如innerText、textContent、src、href等(另外,屬性不被視為元素的子節點)。但在特定複雜的DOM操作時,仍有機會探討,例如選取特定類型的節點。
四、DOM遍歷
『遍歷』這個術語有些一言難盡,它的意思是:在物件樹中遍歷、訪問和操作各個節點的過程。你可以從一個特定的節點開始,依據該節點的父子、兄弟關係來訪問其他節點。
稍微修改上面那段範例,若把起始節點定在 <div id=”box”>,看看搭配不同的屬性方法,console.log()回傳的結果是什麼:
<div id="wrap">
<h1>節點關係</h1>
<div id="box">
<a href="#">1</a>
<a href="#">2</a>
<a href="#">3</a>
</div>
<button>按鈕</button>
</div>
<script>
let startNode = document.getElementById("box");
console.log(屬性方法)
</script>
屬性方法 | 結果 | 說明 |
startNode.parentNode | div#wrap | 回傳父層節點 |
startNode.childNodes | NodeList(3) [a, a, a] | 回傳所有子節點(請看Tips說明) |
startNode.parentElement | div#wrap | 回傳父層元素 |
startNode.children | HTMLCollection(3) [a, a, a] | 回傳所有子元素 |
startNode.firstChild | <a href=”#”>1</a> | 回傳第一個任何類型的子節點 |
startNode.firstElementChild | <a href=”#”>1</a> | 回傳第一個子元素節點 |
startNode.nextSibling | <button>按鈕</button> | 回傳下一個兄弟節點 |
startNode.previousSibling | <h1>節點關係</h1> | 回傳上一個兄弟節點 |
startNode.nodeType | 1 | 回傳節點類型的編號 |
回傳類型為NodeList時,返回所有類型的節點(除了屬性以外);而HTMLCollection僅包含元素節點。
五、DOM選擇元素
在上一段的範例中,就有使用查詢並選擇元素的方法:
let startNode = document.getElementById("box");
此步驟使用getElementById()選取id為box的元素,並將此元素宣告為startNode,這個階段還沒正式執行DOM操作,僅是查詢符合條件的元素節點,並存起來備用。
其他選擇元素的方法還有:
函數方法 | 說明 | 範例 |
getElementById() | 填入元素id名稱,只回傳第一個符合條件的元素。 (一般來說,任何id都是唯一的,但如果有兩個以上的相同id,也只會回傳第一個) | getElementById(“box”) |
getElementsByClassName() | 填入元素的樣式名稱,回傳所有符合條件的元素(HTMLCollection) | getElementsByClassName(“bg-red”) |
getElementsByTagName() | 填入元素名稱,回傳所有符合條件的元素(HTMLCollection) | getElementsByTagName(“a”)、 getElementsByTagName(“*”) |
querySelector() | 填入元素/id/樣式名稱,只回傳第一個符合條件的元素 | querySelector(“div”)、querySelector(“#box”)、 querySelector(“.bg-red”) |
querySelectorAll() | 填入元素/樣式名稱,回傳所有符合條件的元素節點(NodeList) | querySelectorAll(“div”)、 querySelectorAll(“.bg-red”) |
六、DOM操作
上一步已經查詢到並選擇符合條件的元素了,接著就是DOM操作。網路上的文章在定義行為時,通常會把第五段的訪問、選擇放在操作裡面,畢竟選取元素就是為了後續的DOM操作。但我想能用中文清楚地闡述,所以把選擇和操作分開。
DOM操作的具體作法,再度使用同個HTML範例:
<div id="wrap">
<h1>節點關係</h1>
<div id="box">
<a href="#">1</a>
<a href="#">2</a>
<a href="#">3</a>
</div>
<button>按鈕</button>
</div>
<script>
let startNode = document.getElementById("box");
//以下對startNode進行操作...
</script>
6-1. 變更CSS
此範例替startNode加上HTML行內樣式
startNode.style.backgroundColor = "green";
6-2. 新增節點
此範例在startNode中,新增一個<a>元素子節點:
let newElement = document.createElement("a");
startNode.appendChild(newElement);
appendChild()是加在既有子節點的最後方,也可以使用insertBefore(newNode, referenceNode)在最前方加入:
let newElement = document.createElement("a");
let firstChild = startNode.firstChild;//找到第一個子節點
startNode.insertBefore(newElement, firstChild);
6-3. 變更節點屬性
可以變更既有的屬性,例如大多數的標籤都有textContent屬性,而<a>標籤有href屬性:
let newElement = document.createElement("a");
newElement.textContent = "新元素";
newElement.href="https://google.com";
startNode.appendChild(newElement);
用setAttribute(name, value)函數也有同樣的效果:
newElement.setAttribute("href", "https://google.com");
6-4. 刪除節點
刪除節點的方法是Element.remove(),以下選擇索引值為1的子元素,並將它刪除:
startNode.children[1].remove()
這個範例的結果,就是刪除了第二個<a>元素節點。
6-5. 新增事件
function alertEvent() {
alert("新增事件")
}
alertEvent() //執行
6-6. 對事件做出反應
以下要介紹監聽事件addEventListener(),這是一個讓使用者與網頁產生互動很重要的方法。
element.addEventListener("event", function, useCapture)
Event事件有很多,以下列舉一些常用的事件:
類型 | event名稱 | 說明 |
MouseEvent | click、mouseover、mouseout | 滑鼠相關的事件,點按、移進範圍、移出範圍 |
UiEvent | resize、scroll、load | 視窗相關事件,縮放、滾動、載入內容 |
其他常用 | submit、change、copy、paste | 表單送出、內容變化、複製、貼上 |
其中最常使用的事件,莫過於“click”了,以下用“click”作為範例,當按鈕被點選時,觸發事件:
<button id="click-event">按鈕</button>
<div>按鈕尚未被點選</div>
<script>
let btn = document.querySelector("#click-event");
let times = 0;
btn.addEventListener("click", function () {
let textBlock = btn.nextElementSibling;
times += 1;
textBlock.textContent = `按鈕被點選${times}次`;
});
</script>