Javascript 特性和屬性(Attributes and properties)

2023-02-17 10:54 更新

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

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

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

DOM 屬性

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

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

例如,讓我們在 document.body 中創(chuàng)建一個新的屬性:

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

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

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

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

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

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

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 對象一樣:

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

HTML 特性

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

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

例如:

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

請注意,一個元素的標(biāo)準(zhǔn)的特性對于另一個元素可能是未知的。例如 "type" 是 <input> 的一個標(biāo)準(zhǔn)的特性(HTMLInputElement),但對于 <body>HTMLBodyElement)來說則不是。規(guī)范中對相應(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 屬性沒有被創(chuàng)建,因?yàn)樗皇且粋€標(biāo)準(zhǔn)的特性
  </script>
</body>

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

當(dāng)然。所有特性都可以通過使用以下方法進(jìn)行訪問:

  • ?elem.hasAttribute(name)? —— 檢查特性是否存在。
  • ?elem.getAttribute(name)? —— 獲取這個特性值。
  • ?elem.setAttribute(name, value)? —— 設(shè)置這個特性值。
  • ?elem.removeAttribute(name)? —— 移除這個特性。

這些方法操作的實(shí)際上是 HTML 中的內(nèi)容。

我們也可以使用 elem.attributes 讀取所有特性:屬于內(nèi)建 Attr 類的對象的集合,具有 name 和 value 屬性。

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

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

HTML 特性有以下幾個特征:

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

下面是一個使用特性的擴(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>

請注意:

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

屬性—特性同步

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

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

<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 只能從特性同步到屬性,反過來則不行:

<input>

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

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

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

在上面這個例子中:

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

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

DOM 屬性是多類型的

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

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

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

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

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

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

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

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

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

這里有一個例子:

<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 中所寫的完全相同的特性,則可以使用 getAttribute。

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

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

有時,非標(biāo)準(zhǔn)的特性常常用于將自定義的數(shù)據(jù)從 HTML 傳遞到 JavaScript,或者用于為 JavaScript “標(biāo)記” HTML 元素。

像這樣:

<!-- 標(biāo)記這個 div 以在這顯示 "name" -->
<div show-info="name"></div>
<!-- 標(biāo)記這個 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)樘匦灾蹈菀坠芾怼N覀兛梢暂p松地更改狀態(tài):

// 比刪除舊的或者添加一個新的類要簡單一些
div.setAttribute('order-state', 'canceled');

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

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

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

例如,如果一個 elem 有一個名為 "data-about" 的特性,那么可以通過 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ò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 會更新相應(yīng)的視圖:在上面這個例子中的最后一行 (*) 將顏色更改為了藍(lán)色。

總結(jié)

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

簡略的對比:

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

操作特性的方法:

  • ?elem.hasAttribute(name)? —— 檢查是否存在這個特性。
  • ?elem.getAttribute(name)? —— 獲取這個特性值。
  • ?elem.setAttribute(name, value)? —— 設(shè)置這個特性值。
  • ?elem.removeAttribute(name)? —— 移除這個特性。
  • ?elem.attributes? —— 所有特性的集合。

在大多數(shù)情況下,最好使用 DOM 屬性。僅當(dāng) DOM 屬性無法滿足開發(fā)需求,并且我們真的需要特性時,才使用特性,例如:

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

任務(wù)


獲取特性

重要程度: 5

編寫代碼,從文檔(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

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

如果一個鏈接是外部的:

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

例如:

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

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



解決方案

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

這里有兩種方式。

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

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

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

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

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

  link.style.color = 'orange';
}

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

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

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

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


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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號