【DOM】文件物件模型,又為HTML文件標準模型

榮獲日本ArtsSreet繪圖比賽8月主題【日常】的中午賞

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.parentNodediv#wrap回傳父層節點
startNode.childNodesNodeList(3) [a, a, a]回傳所有子節點(請看Tips說明)
startNode.parentElementdiv#wrap回傳父層元素
startNode.childrenHTMLCollection(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.nodeType1回傳節點類型的編號
在HTML代碼中如果存在換行跟空格,有可能會出現多餘的Text節點。以上述例子來說, startNode.childNodes因為a標籤之間有換行,就會出現Text節點,節點的值(nodeValue)為空白換行符號。
回傳NodeList與HTMLCollection有何不同?
回傳類型為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名稱說明
MouseEventclick、mouseover、mouseout滑鼠相關的事件,點按、移進範圍、移出範圍
UiEventresize、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>
按鈕尚未被點選