Javascript 特性和屬性(Attributes and properties)

2023-02-17 10:54 更新

當(dāng)瀏覽器加載頁(yè)面時(shí),它會(huì)“讀取”(或者稱之為:“解析”)HTML 并從中生成 DOM 對(duì)象。對(duì)于元素節(jié)點(diǎn),大多數(shù)標(biāo)準(zhǔn)的 HTML 特性(attributes)會(huì)自動(dòng)變成 DOM 對(duì)象的屬性(properties)。(譯注:attribute 和 property 兩詞意思相近,為作區(qū)分,全文將 attribute 譯為“特性”,property 譯為“屬性”,請(qǐng)讀者注意區(qū)分。)

例如,如果標(biāo)簽是 <body id="page">,那么 DOM 對(duì)象就會(huì)有 body.id="page"。

但特性—屬性映射并不是一一對(duì)應(yīng)的!在本章,我們將帶領(lǐng)你一起分清楚這兩個(gè)概念,了解如何使用它們,了解它們何時(shí)相同何時(shí)不同。

DOM 屬性

我們已經(jīng)見(jiàn)過(guò)了內(nèi)建 DOM 屬性。它們數(shù)量龐大。但是從技術(shù)上講,沒(méi)有人會(huì)限制我們,如果我們覺(jué)得這些 DOM 還不夠,我們可以添加我們自己的。

DOM 節(jié)點(diǎn)是常規(guī)的 JavaScript 對(duì)象。我們可以更改它們。

例如,讓我們?cè)?nbsp;document.body 中創(chuàng)建一個(gè)新的屬性:

document.body.myData = {
  name: 'Caesar',
  title: 'Imperator'
};

alert(document.body.myData.title); // Imperator

我們也可以像下面這樣添加一個(gè)方法:

document.body.sayTagName = function() {
  alert(this.tagName);
};

document.body.sayTagName(); // BODY(這個(gè)方法中的 "this" 的值是 document.body)

我們還可以修改內(nèi)建屬性的原型,例如修改 Element.prototype 為所有元素添加一個(gè)新方法:

Element.prototype.sayHi = function() {
  alert(`Hello, I'm ${this.tagName}`);
};

document.documentElement.sayHi(); // Hello, I'm HTML
document.body.sayHi(); // Hello, I'm BODY

所以,DOM 屬性和方法的行為就像常規(guī)的 Javascript 對(duì)象一樣:

  • 它們可以有很多值。
  • 它們是大小寫(xiě)敏感的(要寫(xiě)成 ?elem.nodeType?,而不是 ?elem.NoDeTyPe?)。

HTML 特性

在 HTML 中,標(biāo)簽可能擁有特性(attributes)。當(dāng)瀏覽器解析 HTML 文本,并根據(jù)標(biāo)簽創(chuàng)建 DOM 對(duì)象時(shí),瀏覽器會(huì)辨別 標(biāo)準(zhǔn)的 特性并以此創(chuàng)建 DOM 屬性。

所以,當(dāng)一個(gè)元素有 id 或其他 標(biāo)準(zhǔn)的 特性,那么就會(huì)生成對(duì)應(yīng)的 DOM 屬性。但是非 標(biāo)準(zhǔn)的 特性則不會(huì)。

例如:

<body id="test" something="non-standard">
  <script>
    alert(document.body.id); // test
    // 非標(biāo)準(zhǔn)的特性沒(méi)有獲得對(duì)應(yīng)的屬性
    alert(document.body.something); // undefined
  </script>
</body>

請(qǐng)注意,一個(gè)元素的標(biāo)準(zhǔn)的特性對(duì)于另一個(gè)元素可能是未知的。例如 "type" 是 <input> 的一個(gè)標(biāo)準(zhǔn)的特性(HTMLInputElement),但對(duì)于 <body>HTMLBodyElement)來(lái)說(shuō)則不是。規(guī)范中對(duì)相應(yīng)元素類的標(biāo)準(zhǔn)的屬性進(jìn)行了詳細(xì)的描述。

這里我們可以看到:

<body id="body" type="...">
  <input id="input" type="text">
  <script>
    alert(input.type); // text
    alert(body.type); // undefined:DOM 屬性沒(méi)有被創(chuàng)建,因?yàn)樗皇且粋€(gè)標(biāo)準(zhǔn)的特性
  </script>
</body>

所以,如果一個(gè)特性不是標(biāo)準(zhǔn)的,那么就沒(méi)有相對(duì)應(yīng)的 DOM 屬性。那我們有什么方法來(lái)訪問(wèn)這些特性嗎?

當(dāng)然。所有特性都可以通過(guò)使用以下方法進(jìn)行訪問(wè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 類的對(duì)象的集合,具有 name 和 value 屬性。

下面是一個(gè)讀取非標(biāo)準(zhǔn)的特性的示例:

<body something="non-standard">
  <script>
    alert(document.body.getAttribute('something')); // non-standard
  </script>
</body>

HTML 特性有以下幾個(gè)特征:

  • 它們的名字是大小寫(xiě)不敏感的(?id? 與 ?ID? 相同)。
  • 它們的值總是字符串類型的。

下面是一個(gè)使用特性的擴(kuò)展示例:

<body>
  <div id="elem" about="Elephant"></div>

  <script>
    alert( elem.getAttribute('About') ); // (1) 'Elephant',讀取

    elem.setAttribute('Test', 123); // (2) 寫(xiě)入

    alert( elem.outerHTML ); // (3) 查看特性是否在 HTML 中(在)

    for (let attr of elem.attributes) { // (4) 列出所有
      alert( `${attr.name} = ${attr.value}` );
    }
  </script>
</body>

請(qǐng)注意:

  1. ?getAttribute('About')? —— 這里的第一個(gè)字母是大寫(xiě)的,但是在 HTML 中,它們都是小寫(xiě)的。但這沒(méi)有影響:特性的名稱是大小寫(xiě)不敏感的。
  2. 我們可以將任何東西賦值給特性,但是這些東西會(huì)變成字符串類型的。所以這里我們的值為 ?"123"?。
  3. 所有特性,包括我們?cè)O(shè)置的那個(gè)特性,在 ?outerHTML? 中都是可見(jiàn)的。
  4. ?attributes? 集合是可迭代對(duì)象,該對(duì)象將所有元素的特性(標(biāo)準(zhǔn)和非標(biāo)準(zhǔn)的)作為 ?name? 和 ?value? 屬性存儲(chǔ)在對(duì)象中。

屬性—特性同步

當(dāng)一個(gè)標(biāo)準(zhǔn)的特性被改變,對(duì)應(yīng)的屬性也會(huì)自動(dòng)更新,(除了幾個(gè)特例)反之亦然。

在下面這個(gè)示例中,id 被修改為特性,我們可以看到對(duì)應(yīng)的屬性也發(fā)生了變化。然后反過(guò)來(lái)也是同樣的效果:

<input>

<script>
  let input = document.querySelector('input');

  // 特性 => 屬性
  input.setAttribute('id', 'id');
  alert(input.id); // id(被更新了)

  // 屬性 => 特性
  input.id = 'newId';
  alert(input.getAttribute('id')); // newId(被更新了)
</script>

但這里也有些例外,例如 input.value 只能從特性同步到屬性,反過(guò)來(lái)則不行:

<input>

<script>
  let input = document.querySelector('input');

  // 特性 => 屬性
  input.setAttribute('value', 'text');
  alert(input.value); // text

  // 這個(gè)操作無(wú)效,屬性 => 特性
  input.value = 'newValue';
  alert(input.getAttribute('value')); // text(沒(méi)有被更新?。?</script>

在上面這個(gè)例子中:

  • 改變特性值 ?value? 會(huì)更新屬性。
  • 但是屬性的更改不會(huì)影響特性。

這個(gè)“功能”在實(shí)際中會(huì)派上用場(chǎng),因?yàn)橛脩粜袨榭赡軙?huì)導(dǎo)致 value 的更改,然后在這些操作之后,如果我們想從 HTML 中恢復(fù)“原始”值,那么該值就在特性中。

DOM 屬性是多類型的

DOM 屬性不總是字符串類型的。例如,input.checked 屬性(對(duì)于 checkbox 的)是布爾型的。

<input id="input" type="checkbox" checked> checkbox

<script>
  alert(input.getAttribute('checked')); // 特性值是:空字符串
  alert(input.checked); // 屬性值是:true
</script>

還有其他的例子。style 特性是字符串類型的,但 style 屬性是一個(gè)對(duì)象:

<div id="div" style="color:red;font-size:120%">Hello</div>

<script>
  // 字符串
  alert(div.getAttribute('style')); // color:red;font-size:120%

  // 對(duì)象
  alert(div.style); // [object CSSStyleDeclaration]
  alert(div.style.color); // red
</script>

盡管大多數(shù) DOM 屬性都是字符串類型的。

有一種非常少見(jiàn)的情況,即使一個(gè) DOM 屬性是字符串類型的,但它可能和 HTML 特性也是不同的。例如,href DOM 屬性一直是一個(gè) 完整的 URL,即使該特性包含一個(gè)相對(duì)路徑或者包含一個(gè) #hash。

這里有一個(gè)例子:

<a id="a" href="#hello">link</a>
<script>
  // 特性
  alert(a.getAttribute('href')); // #hello

  // 屬性
  alert(a.href ); // http://site.com/page#hello 形式的完整 URL
</script>

如果我們需要 href 特性的值,或者其他與 HTML 中所寫(xiě)的完全相同的特性,則可以使用 getAttribute。

非標(biāo)準(zhǔn)的特性,dataset

當(dāng)編寫(xiě) HTML 時(shí),我們會(huì)用到很多標(biāo)準(zhǔn)的特性。但是非標(biāo)準(zhǔn)的,自定義的呢?首先,讓我們看看它們是否有用?用來(lái)做什么?

有時(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>

它們還可以用來(lái)設(shè)置元素的樣式。

例如,這里使用 order-state 特性來(lái)設(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è)新的類要簡(jiǎn)單一些
div.setAttribute('order-state', 'canceled');

但是自定義的特性也存在問(wèn)題。如果我們出于我們的目的使用了非標(biāo)準(zhǔn)的特性,之后它被引入到了標(biāo)準(zhǔn)中并有了其自己的用途,該怎么辦?HTML 語(yǔ)言是在不斷發(fā)展的,并且更多的特性出現(xiàn)在了標(biāo)準(zhǔn)中,以滿足開(kāi)發(fā)者的需求。在這種情況下,自定義的屬性可能會(huì)產(chǎn)生意料不到的影響。

為了避免沖突,存在 data-* 特性。

所有以 “data-” 開(kāi)頭的特性均被保留供程序員使用。它們可在 dataset 屬性中使用。

例如,如果一個(gè) elem 有一個(gè)名為 "data-about" 的特性,那么可以通過(guò) elem.dataset.about 取到它。

像這樣:

<body data-about="Elephants">
<script>
  alert(document.body.dataset.about); // Elephants
</script>

像 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ù)的方式。

請(qǐng)注意,我們不僅可以讀取數(shù)據(jù),還可以修改數(shù)據(jù)屬性(data-attributes)。然后 CSS 會(huì)更新相應(yīng)的視圖:在上面這個(gè)例子中的最后一行 (*) 將顏色更改為了藍(lán)色。

總結(jié)

  • 特性(attribute)—— 寫(xiě)在 HTML 中的內(nèi)容。
  • 屬性(property)—— DOM 對(duì)象中的內(nèi)容。

簡(jiǎn)略的對(duì)比:

屬性 特性
類型 任何值,標(biāo)準(zhǔn)的屬性具有規(guī)范中描述的類型 字符串
名字 名字(name)是大小寫(xiě)敏感的 名字(name)是大小寫(xiě)不敏感的

操作特性的方法:

  • ?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 屬性無(wú)法滿足開(kāi)發(fā)需求,并且我們真的需要特性時(shí),才使用特性,例如:

  • 我們需要一個(gè)非標(biāo)準(zhǔn)的特性。但是如果它以 ?data-? 開(kāi)頭,那么我們應(yīng)該使用 ?dataset?。
  • 我們想要讀取 HTML 中“所寫(xiě)的”值。對(duì)應(yīng)的 DOM 屬性可能不同,例如 ?href? 屬性一直是一個(gè) 完整的 URL,但是我們想要的是“原始的”值。

任務(wù)


獲取特性

重要程度: 5

編寫(xiě)代碼,從文檔(document)中獲取帶有 data-widget-name 特性(attribute)的元素,并讀取它的值。

<!DOCTYPE html>
<html>
<body>

  <div data-widget-name="menu">Choose the genre</div>

  <script>
    /* your code */
  </script>
</body>
</html>

解決方案

<!DOCTYPE html>
<html>
<body>

  <div data-widget-name="menu">Choose the genre</div>

  <script>
    // 獲取它
    let elem = document.querySelector('[data-widget-name]');

    // 讀取值
    alert(elem.dataset.widgetName);
    // 或
    alert(elem.getAttribute('data-widget-name'));
  </script>
</body>
</html>

將外部鏈接設(shè)為橙色

重要程度: 3

通過(guò)修改 style 屬性,將所有外部鏈接變?yōu)槌壬?

如果一個(gè)鏈接是外部的:

  • 其 ?href? 中包含 ?://?
  • 但不是以 ?http://internal.com? 開(kāi)頭。

例如:

<a name="list">the list</a>
<ul>
  <li><a  rel="external nofollow" target="_blank" >http://google.com</a></li>
  <li><a href="/tutorial">/tutorial.html</a></li>
  <li><a href="local/path">local/path</a></li>
  <li><a href="ftp://ftp.com/my.zip" rel="external nofollow" target="_blank" >ftp://ftp.com/my.zip</a></li>
  <li><a  rel="external nofollow" target="_blank" >http://nodejs.org</a></li>
  <li><a  rel="external nofollow" target="_blank" >http://internal.com/test</a></li>
</ul>

<script>
  // 為單個(gè)鏈接設(shè)置樣式
  let link = document.querySelector('a');
  link.style.color = 'orange';
</script>

結(jié)果應(yīng)該是:



解決方案

首先,我們需要找到所有外部鏈接。

這里有兩種方式。

第一種是使用 document.querySelectorAll('a') 找到所有鏈接,然后過(guò)濾出我們需要的部分:

let links = document.querySelectorAll('a');

for (let link of links) {
  let href = link.getAttribute('href');
  if (!href) continue; // 沒(méi)有特性

  if (!href.includes('://')) continue; // 沒(méi)有協(xié)議

  if (href.startsWith('http://internal.com')) continue; // 內(nèi)部的

  link.style.color = 'orange';
}

請(qǐng)注意:我們用的是 link.getAttribute('href')。而不是 link.href,因?yàn)槲覀冃枰氖莵?lái)自 HTML 的值。

……另一種更簡(jiǎn)單的方法,是使用 CSS 選擇器進(jìn)行檢查:

// 查找所有 href 中包含 :// 的鏈接
// 但 href 不是以 http://internal.com 開(kāi)頭
let selector = 'a[href*="://"]:not([href^="http://internal.com"])';
let links = document.querySelectorAll(selector);

links.forEach(link => link.style.color = 'orange');


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)