當(dāng)瀏覽器加載頁面時(shí),它會(huì)“讀取”(或者稱之為:“解析”)HTML 并從中生成 DOM 對象。對于元素節(jié)點(diǎn),大多數(shù)標(biāo)準(zhǔn)的 HTML 特性(attributes)會(huì)自動(dòng)變成 DOM 對象的屬性(properties)。(譯注:attribute 和 property 兩詞意思相近,為作區(qū)分,全文將 attribute 譯為“特性”,property 譯為“屬性”,請讀者注意區(qū)分。)
例如,如果標(biāo)簽是 <body id="page">
,那么 DOM 對象就會(huì)有 body.id="page"
。
但特性—屬性映射并不是一一對應(yīng)的!在本章,我們將帶領(lǐng)你一起分清楚這兩個(gè)概念,了解如何使用它們,了解它們何時(shí)相同何時(shí)不同。
DOM 屬性
我們已經(jīng)見過了內(nèi)建 DOM 屬性。它們數(shù)量龐大。但是從技術(shù)上講,沒有人會(huì)限制我們,如果我們覺得這些 DOM 還不夠,我們可以添加我們自己的。
DOM 節(jié)點(diǎn)是常規(guī)的 JavaScript 對象。我們可以更改它們。
例如,讓我們在 document.body
中創(chuàng)建一個(gè)新的屬性:
我們也可以像下面這樣添加一個(gè)方法:
我們還可以修改內(nèi)建屬性的原型,例如修改 Element.prototype
為所有元素添加一個(gè)新方法:
所以,DOM 屬性和方法的行為就像常規(guī)的 Javascript 對象一樣:
- 它們可以有很多值。
- 它們是大小寫敏感的(要寫成
elem.nodeType
,而不是elem.NoDeTyPe
)。
HTML 特性
在 HTML 中,標(biāo)簽可能擁有特性(attributes)。當(dāng)瀏覽器解析 HTML 文本,并根據(jù)標(biāo)簽創(chuàng)建 DOM 對象時(shí),瀏覽器會(huì)辨別 標(biāo)準(zhǔn)的 特性并以此創(chuàng)建 DOM 屬性。
所以,當(dāng)一個(gè)元素有 id
或其他 標(biāo)準(zhǔn)的 特性,那么就會(huì)生成對應(yīng)的 DOM 屬性。但是非 標(biāo)準(zhǔn)的 特性則不會(huì)。
例如:
請注意,一個(gè)元素的標(biāo)準(zhǔn)的特性對于另一個(gè)元素可能是未知的。例如 "type"
是 <input>
的一個(gè)標(biāo)準(zhǔn)的特性(HTMLInputElement),但對于 <body>
(HTMLBodyElement)來說則不是。規(guī)范中對相應(yīng)元素類的標(biāo)準(zhǔn)的屬性進(jìn)行了詳細(xì)的描述。
這里我們可以看到:
所以,如果一個(gè)特性不是標(biāo)準(zhǔn)的,那么就沒有相對應(yīng)的 DOM 屬性。那我們有什么方法來訪問這些特性嗎?
當(dāng)然。所有特性都可以通過使用以下方法進(jìn)行訪問:
elem.hasAttribute(name)
—— 檢查特性是否存在。elem.getAttribute(name)
—— 獲取這個(gè)特性值。elem.setAttribute(name, value)
—— 設(shè)置這個(gè)特性值。elem.removeAttribute(name)
—— 移除這個(gè)特性。
這些方法操作的實(shí)際上是 HTML 中的內(nèi)容。
我們也可以使用 elem.attributes
讀取所有特性:屬于內(nèi)建 Attr 類的對象的集合,具有 name
和 value
屬性。
下面是一個(gè)讀取非標(biāo)準(zhǔn)的特性的示例:
HTML 特性有以下幾個(gè)特征:
- 它們的名字是大小寫不敏感的(
id
與ID
相同)。 - 它們的值總是字符串類型的。
下面是一個(gè)使用特性的擴(kuò)展示例:
<body>
<div id="elem" about="Elephant"></div>
<script>
alert( elem.getAttribute('About') ); // (1) 'Elephant',讀取
elem.setAttribute('Test', 123); // (2) 寫入
alert( elem.outerHTML ); // (3) 查看特性是否在 HTML 中(在)
for (let attr of elem.attributes) { // (4) 列出所有
alert( `${attr.name} = ${attr.value}` );
}
</script>
</body>
請注意:
getAttribute('About')
—— 這里的第一個(gè)字母是大寫的,但是在 HTML 中,它們都是小寫的。但這沒有影響:特性的名稱是大小寫不敏感的。- 我們可以將任何東西賦值給特性,但是這些東西會(huì)變成字符串類型的。所以這里我們的值為
"123"
。 - 所有特性,包括我們設(shè)置的那個(gè)特性,在
outerHTML
中都是可見的。 attributes
集合是可迭代對象,該對象將所有元素的特性(標(biāo)準(zhǔn)和非標(biāo)準(zhǔn)的)作為name
和value
屬性存儲(chǔ)在對象中。
屬性—特性同步
當(dāng)一個(gè)標(biāo)準(zhǔn)的特性被改變,對應(yīng)的屬性也會(huì)自動(dòng)更新,(除了幾個(gè)特例)反之亦然。
在下面這個(gè)示例中,id
被修改為特性,我們可以看到對應(yīng)的屬性也發(fā)生了變化。然后反過來也是同樣的效果:
但這里也有些例外,例如 input.value
只能從特性同步到屬性,反過來則不行:
在上面這個(gè)例子中:
- 改變特性值
value
會(huì)更新屬性。 - 但是屬性的更改不會(huì)影響特性。
這個(gè)“功能”在實(shí)際中會(huì)派上用場,因?yàn)橛脩粜袨榭赡軙?huì)導(dǎo)致 value
的更改,然后在這些操作之后,如果我們想從 HTML 中恢復(fù)“原始”值,那么該值就在特性中。
DOM 屬性是多類型的
DOM 屬性不總是字符串類型的。例如,input.checked
屬性(對于 checkbox 的)是布爾型的。
還有其他的例子。style
特性是字符串類型的,但 style
屬性是一個(gè)對象:
盡管大多數(shù) DOM 屬性都是字符串類型的。
有一種非常少見的情況,即使一個(gè) DOM 屬性是字符串類型的,但它可能和 HTML 特性也是不同的。例如,href
DOM 屬性一直是一個(gè) 完整的 URL,即使該特性包含一個(gè)相對路徑或者包含一個(gè) #hash
。
這里有一個(gè)例子:
如果我們需要 href
特性的值,或者其他與 HTML 中所寫的完全相同的特性,則可以使用 getAttribute
。
非標(biāo)準(zhǔn)的特性,dataset
當(dāng)編寫 HTML 時(shí),我們會(huì)用到很多標(biāo)準(zhǔn)的特性。但是非標(biāo)準(zhǔn)的,自定義的呢?首先,讓我們看看它們是否有用?用來做什么?
有時(shí),非標(biāo)準(zhǔn)的特性常常用于將自定義的數(shù)據(jù)從 HTML 傳遞到 JavaScript,或者用于為 JavaScript “標(biāo)記” HTML 元素。
像這樣:
<!-- 標(biāo)記這個(gè) div 以在這顯示 "name" -->
<div show-info="name"></div>
<!-- 標(biāo)記這個(gè) div 以在這顯示 "age" -->
<div show-info="age"></div>
<script>
// 這段代碼找到帶有標(biāo)記的元素,并顯示需要的內(nèi)容
let user = {
name: "Pete",
age: 25
};
for(let div of document.querySelectorAll('[show-info]')) {
// 在字段中插入相應(yīng)的信息
let field = div.getAttribute('show-info');
div.innerHTML = user[field]; // 首先 "name" 變?yōu)?Pete,然后 "age" 變?yōu)?25
}
</script>
它們還可以用來設(shè)置元素的樣式。
例如,這里使用 order-state
特性來設(shè)置訂單狀態(tài):
<style>
/* 樣式依賴于自定義特性 "order-state" */
.order[order-state="new"] {
color: green;
}
.order[order-state="pending"] {
color: blue;
}
.order[order-state="canceled"] {
color: red;
}
</style>
<div class="order" order-state="new">
A new order.
</div>
<div class="order" order-state="pending">
A pending order.
</div>
<div class="order" order-state="canceled">
A canceled order.
</div>
為什么使用特性比使用 .order-state-new
,.order-state-pending
,.order-state-canceled
這些樣式類要好?
因?yàn)樘匦灾蹈菀坠芾?。我們可以輕松地更改狀態(tài):
// 比刪除舊的或者添加一個(gè)新的類要簡單一些
div.setAttribute('order-state', 'canceled');
但是自定義的特性也存在問題。如果我們出于我們的目的使用了非標(biāo)準(zhǔn)的特性,之后它被引入到了標(biāo)準(zhǔn)中并有了其自己的用途,該怎么辦?HTML 語言是在不斷發(fā)展的,并且更多的特性出現(xiàn)在了標(biāo)準(zhǔn)中,以滿足開發(fā)者的需求。在這種情況下,自定義的屬性可能會(huì)產(chǎn)生意料不到的影響。
為了避免沖突,存在 data-* 特性。
所有以 “data-” 開頭的特性均被保留供程序員使用。它們可在 dataset
屬性中使用。
例如,如果一個(gè) elem
有一個(gè)名為 "data-about"
的特性,那么可以通過 elem.dataset.about
取到它。
像這樣:
像 data-order-state
這樣的多詞特性可以以駝峰式進(jìn)行調(diào)用:dataset.orderState
。
這里是 “order state” 那個(gè)示例的重構(gòu)版:
<style>
.order[data-order-state="new"] {
color: green;
}
.order[data-order-state="pending"] {
color: blue;
}
.order[data-order-state="canceled"] {
color: red;
}
</style>
<div id="order" class="order" data-order-state="new">
A new order.
</div>
<script>
// 讀取
alert(order.dataset.orderState); // new
// 修改
order.dataset.orderState = "pending"; // (*)
</script>
使用 data-*
特性是一種合法且安全的傳遞自定義數(shù)據(jù)的方式。
請注意,我們不僅可以讀取數(shù)據(jù),還可以修改數(shù)據(jù)屬性(data-attributes)。然后 CSS 會(huì)更新相應(yīng)的視圖:在上面這個(gè)例子中的最后一行 (*)
將顏色更改為了藍(lán)色。
總結(jié)
- 特性(attribute)—— 寫在 HTML 中的內(nèi)容。
- 屬性(property)—— DOM 對象中的內(nèi)容。
簡略的對比:
屬性 | 特性 | |
---|---|---|
類型 | 任何值,標(biāo)準(zhǔn)的屬性具有規(guī)范中描述的類型 | 字符串 |
名字 | 名字(name)是大小寫敏感的 | 名字(name)是大小寫不敏感的 |
操作特性的方法:
elem.hasAttribute(name)
—— 檢查是否存在這個(gè)特性。elem.getAttribute(name)
—— 獲取這個(gè)特性值。elem.setAttribute(name, value)
—— 設(shè)置這個(gè)特性值。elem.removeAttribute(name)
—— 移除這個(gè)特性。elem.attributes
—— 所有特性的集合。
在大多數(shù)情況下,最好使用 DOM 屬性。僅當(dāng) DOM 屬性無法滿足開發(fā)需求,并且我們真的需要特性時(shí),才使用特性,例如:
- 我們需要一個(gè)非標(biāo)準(zhǔn)的特性。但是如果它以
data-
開頭,那么我們應(yīng)該使用dataset
。 - 我們想要讀取 HTML 中“所寫的”值。對應(yīng)的 DOM 屬性可能不同,例如
href
屬性一直是一個(gè) 完整的 URL,但是我們想要的是“原始的”值。
更多建議: