文件物件模型(Document Object Model, DOM)是什麼?
簡單來說就是將每個HTML的標籤都物件化,讓JavaScript可以訪問和更改HTML的元素,舉例來說,我們可以點擊按鈕來改變文字。當網頁載入時,瀏覽器會自動幫我們創立這個頁面的DOM。
為何我們需要DOM?
在以前瀏覽器大亂之時,每家瀏覽器的規則都不一樣,讓工程師非常頭疼,直到W3C訂立出了DOM的標準,讓瀏覽器公司可以依照這個準則去設計瀏覽器。
DOM tree與節點(node)
DOM將標籤定義成物件後,會形成一個樹狀結構,也就是DOM tree。
這是我們的HTML檔:
表示為DOM tree後會變成這樣,每個標籤就是一個節點,最上層的是Document
,往下延伸出Element
,再往下還有Text
與Attribute
。
Selecting DOM Elements
getElementById
:根據元素ID選取。getElementsByTagName
:根據標籤名稱選取。(例如< p >、< h1 >....)getElementsByClassName
:根據Class選取。getElementsByName
:根據Name選取。querySelector(selectors)
:根據selectors選取。(可使用範圍很廣)
HTMLCollection
HTMLCollection是元素(Element)的集合,並提供可選取集合成員的方法與屬性。
如果我們使用getElementById
去選取h1
,console中會出現HTMLCollection
,它看起來長得有點像Array(有length屬性和長得像index的item()),但實質上它是Object,所以不能用陣列方法處理(像是map()
、filter()
...),但用for
可以遍歷。
NodeList
NodeList
也是物件,是節點的集合。不像是HTMLCollection
只能儲存Element,NodeList
同時包含了Text
、Attribute
...等等。可以看到圖片上NodeList
比HTMLCollection
多包涵了6個Text
如何得到HTMLCollection和NodeList物件?
- HTMLCollection:
children
、getElementsByTagName()
- NodeList:
childNodes
、querySelectorAll()
注意:
- 使用
childNodes
回傳的NodeList為動態集合(live collection)。 - 使用
querySelectorAll()
回傳的NodeList為靜態集合(static collection)。 - HTMLCollection具有即時性(live),如果document物件發生改變,HTMLCollection物件也會跟著做變化。
textContent與innerText
textContent
與innerText
皆是DOM的屬性,兩者名字很像,容易混淆。
差異:
textContent
- 標準化用法
- get所有元素的內容,包含
<script>
、<style>
元素 - 返回每個節點的元素
innerText
- 非標準化用法
- 只顯示“人眼可見”的元素,隱藏的內容不會顯示出來。
- 在計算中加入了CSS樣式,知道Styling長怎樣,但也會造成應用程序下降
示範textContent與innerText的差異結果,使用以下HTML:
<body>
<p id='para'>
<style>
#para {
color: blue
}
</style>
HI!!!!!!<br>I'm your new classmate</p>
<h4>textContent:</h4>
<textarea id="textContent" rows="4" cols="20" readonly>...</textarea>
<h4>innerText:</h4>
<textarea id="innerText" rows="4" cols="20" readonly>...</textarea>
</body>
使用以下JavaScript:
let para = document.querySelector('#para');
let textContent = document.querySelector('#textContent');
let innerText = document.querySelector('#innerText');
//textContent顯示:
textContent.value = para.textContent;
<!--"
#para {
color: blue
}
HI!!!!!!I'm your new classmate"-->
//innerText顯示:
innerText.value = para.innerText;
<!--"HI!!!!!!
I'm your new classmate"-->
頁面顯示結果:
Attribute與Property的差別
當瀏覽器加載頁面時,它會解析HTML並從中生成DOM property。 大多數形況下,HTML attribute會自動成為DOM property。
舉例來說,如果tag為<div class = 'introduction'>
,則DOM property也會具有div.class = 'introduction'
。
但是必須注意attribute與property的映射不是一對一的,下面會詳細介紹它們的相異之處。
Attributes
Attributes是我們寫在HTML的屬性,像是<div type = 'text'>
。
Properties
Properties是存在在DOM objects中的屬性,像是div.type
。
Attribute要符合相對應的Element class
當我們的element有符合標準的的attribute時,相對應的property就會生成。但是當element不符合標準時,相對應的property就不會生成。
像是input元素有type這個attribute(符合標準),但body元素沒有type(不符合標準),所以不會生成相應的property。
那我們要如何取得不符合標準的attribute呢?
以下有幾種方法:
el.hasAttribute()
- 檢查這個attribute有沒有存在el.getAttribute()
- 取得attributeel.setAttribute()
- 設定attribute的valueel.removeAttribute()
- 移除attributeel.attributes
- 將該元素所有attribute的節點返回為一個集合
通常Attribute與Property的之間是同步的
通常Attribute與Property的之間是同步的,意思是說當一個標準的attribute變更,與它相對應的property也會自動更新。
以下範例:
//attribute => property
let p = document.querySelector('p');
p.setAttribute('id', 'paragraph');
p.id; //"paragraph"
//property => attribute
p.id = 'newParagraph';
p.getAttribute('id'); //"newParagraph"
但也有例外的情況,像input.value只能同步attribute => property。
<input id = 'input' type = 'password'>Password
<script>
//attribute => property
input.setAttribute('value', 'password');
input.value; //password
//property =X> attribute
input.value = 'newPassword';
input.getAttribute('value'); //password(沒有更新成newPassword)
</script>
DOM property的值通常為string但也有例外的狀況,例如:當利用element.property取得style屬性時是回傳object,另一種情況:用element.getAttribute取得style的屬性時,會回傳string。
<body id="body" style="background-color:#000;">
</body>
<script>
console.log(body.getAttribute('style'));
//background-color:#000;
console.log(document.body.style);
//CSSStyleDeclaration {0: "background-color", alignContent: "", alignItems: "", alignSelf: "", alignmentBaseline: "", all: "", …}
</script>
相同概念的舉例,input.checked的property是回傳boolean值,而使用getAttribute則會回傳空字串。
<input id = "input" type = "checkbox" checked>Check Box
<script>
console.log(input.getAttribute('checked')); //empty string
console.log(input.checked); //true
</script>
上述提到property大多為string。但少數情形下,儘管property的type是string,attribute也不一定是string。
經典的例子就是URL,看以下的例子可以發現element.href回傳的都是完整URL。這是因為
W3C的規定,href property一定要是完整形式的連結。
<a href='/Users/teagan/Desktop/DOM.html'>DOM</a>
<a href='#1'>first-paragraph</a>
<script>
//選取第一個a連結
let dom = document.querySelectorAll('a')[0];
console.log(dom.getAttribute('href'));
// /Users/teagan/Desktop/DOM.html
console.log(dom.href);
// file:///Users/teagan/Desktop/DOM.html
//選取第二個a連結
let google = document.querySelectorAll('a')[1];
console.log(google.getAttribute('href'))'
// #1
console.log(google.href);
// file:///Users/teagan/Desktop/DOM.html#1
</script>
非標準屬性、數據
有時我們使用非標準屬性去自定義數據,或是為JavaScript標記(mark)HTML元素。
注意,使用data-*
來自定義屬性,這讓JS或CSS更加容易讀取屬性的值。
範例一:取得屬性的值
<body id="body" data-product-category="noodles">
<script>
//使用element.dataset.property取得(注意-(dash)要轉換成camelCase
console.log(document.body.dataset.productCategory) //noodles
//或使用getAttribute
console.log(document.body.getAttribute('data-product-category')) //noodles
</script>
範例二:變更字的顏色與內容
<div class="order" data-order-state="new">
A new order.
</div>
<style>
.order[data-order-state="new"] {
color: black;
}
.order[data-order-state="pending"] {
color: green;
}
</style>
<script>
order.dataset.orderState = "pending";
let padding = document.querySelector('.order');
padding.textContent = 'A padding order';
</script>
執行JS會改變字的顏色與內容:
參考資料: