2022 年 8 月 21 日

屬性和特性

當瀏覽器載入頁面時,它會「讀取」(另一個說法是:「解析」)HTML,並從中產生 DOM 物件。對於元素節點,大多數標準 HTML 屬性會自動成為 DOM 物件的特性。

例如,如果標籤是 <body id="page">,則 DOM 物件有 body.id="page"

但屬性與屬性的對應並非一對一!在本章中,我們將注意區分這兩個概念,了解如何使用它們,以及它們何時相同,何時不同。

DOM 屬性

我們已經看過內建的 DOM 屬性。有很多。但技術上沒有人限制我們,如果還不夠,我們可以新增自己的。

DOM 節點是常規的 JavaScript 物件。我們可以修改它們。

例如,讓我們在 document.body 中建立一個新屬性

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

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

我們也可以新增一個方法

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

document.body.sayTagName(); // BODY (the value of "this" in the method is document.body)

我們還可以修改內建原型,例如 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 屬性和方法的行為就像常規 JavaScript 物件的屬性和方法一樣

  • 它們可以有任何值。
  • 它們區分大小寫(寫 elem.nodeType,而不是 elem.NoDeTyPe)。

HTML 屬性

在 HTML 中,標籤可能具有屬性。當瀏覽器解析 HTML 以為標籤建立 DOM 物件時,它會辨識標準屬性,並從中建立 DOM 屬性。

因此,當元素具有 id 或其他標準屬性時,將建立對應的屬性。但如果屬性是非標準屬性,則不會發生這種情況。

例如

<body id="test" something="non-standard">
  <script>
    alert(document.body.id); // test
    // non-standard attribute does not yield a property
    alert(document.body.something); // undefined
  </script>
</body>

請注意,一個元素的標準屬性可能對另一個元素未知。例如,"type"<input> (HTMLInputElement) 的標準屬性,但不是 <body> (HTMLBodyElement) 的標準屬性。標準屬性在對應元素類別的規格中描述。

我們可以在這裡看到它

<body id="body" type="...">
  <input id="input" type="text">
  <script>
    alert(input.type); // text
    alert(body.type); // undefined: DOM property not created, because it's non-standard
  </script>
</body>

因此,如果屬性是非標準屬性,則不會有 DOM 屬性。是否有辦法存取此類屬性?

當然。可以使用下列方法存取所有屬性

  • elem.hasAttribute(name) – 檢查是否存在。
  • elem.getAttribute(name) – 取得值。
  • elem.setAttribute(name, value) – 設定值。
  • elem.removeAttribute(name) – 移除屬性。

這些方法的操作方式與 HTML 中寫的完全相同。

此外,可以使用 elem.attributes 讀取所有屬性:一個屬於內建 Attr 類別的物件集合,具有 namevalue 屬性。

以下是讀取非標準屬性的範例

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

HTML 屬性具有下列特徵

  • 其名稱不區分大小寫(idID 相同)。
  • 其值永遠是字串。

以下是使用屬性的延伸範例

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

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

    elem.setAttribute('Test', 123); // (2), writing

    alert( elem.outerHTML ); // (3), see if the attribute is in HTML (yes)

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

請注意

  1. getAttribute('About') – 此處的第一個字母為大寫,而在 HTML 中則全部為小寫。但這無關緊要:屬性名稱不區分大小寫。
  2. 我們可以將任何內容指定給屬性,但它會變成字串。因此,我們在此處將 "123" 作為值。
  3. 所有屬性(包括我們設定的屬性)都顯示在 outerHTML 中。
  4. attributes 集合是可迭代的,並具有元素的所有屬性(標準和非標準)作為具有 namevalue 屬性的物件。

屬性-屬性同步

當標準屬性變更時,對應的屬性會自動更新,反之亦然(有一些例外)。

在以下範例中,id 被修改為屬性,我們可以看到屬性也變更了。然後再反向執行相同的動作

<input>

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

  // attribute => property
  input.setAttribute('id', 'id');
  alert(input.id); // id (updated)

  // property => attribute
  input.id = 'newId';
  alert(input.getAttribute('id')); // newId (updated)
</script>

但是有一些例外,例如 input.value 僅從屬性 → 屬性同步,但不會反向同步

<input>

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

  // attribute => property
  input.setAttribute('value', 'text');
  alert(input.value); // text

  // NOT property => attribute
  input.value = 'newValue';
  alert(input.getAttribute('value')); // text (not updated!)
</script>

在上述範例中

  • 變更屬性 value 會更新屬性。
  • 但是屬性變更不會影響屬性。

此「功能」實際上可能很方便,因為使用者動作可能導致 value 變更,然後在這些動作之後,如果我們想要從 HTML 中復原「原始」值,則它會在屬性中。

DOM 屬性有型別

DOM 屬性不總是字串。例如,input.checked 屬性(對於核取方塊)是布林值

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

<script>
  alert(input.getAttribute('checked')); // the attribute value is: empty string
  alert(input.checked); // the property value is: true
</script>

還有其他範例。style 屬性是字串,但 style 屬性是物件

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

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

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

不過,大多數屬性都是字串。

即使 DOM 屬性型別是字串,但它也很少與屬性不同。例如,href DOM 屬性始終是完整的 URL,即使屬性包含相對 URL 或僅包含 #hash

以下是範例

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

  // property
  alert(a.href ); // full URL in the form http://site.com/page#hello
</script>

如果我們需要 href 或任何其他屬性的值,就像在 HTML 中寫入的那樣,我們可以使用 getAttribute

非標準屬性、資料集

在撰寫 HTML 時,我們使用了許多標準屬性。但是非標準的自訂屬性呢?首先,讓我們看看它們是否有用?它們的用途是什麼?

有時非標準屬性用於將自訂資料從 HTML 傳遞到 JavaScript,或用於「標記」JavaScript 的 HTML 元素。

像這樣

<!-- mark the div to show "name" here -->
<div show-info="name"></div>
<!-- and age here -->
<div show-info="age"></div>

<script>
  // the code finds an element with the mark and shows what's requested
  let user = {
    name: "Pete",
    age: 25
  };

  for(let div of document.querySelectorAll('[show-info]')) {
    // insert the corresponding info into the field
    let field = div.getAttribute('show-info');
    div.innerHTML = user[field]; // first Pete into "name", then 25 into "age"
  }
</script>

它們也可以用來設定元素樣式。

例如,這裡的訂單狀態屬性 order-state 被使用

<style>
  /* styles rely on the custom attribute "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 這樣的類別好?

因為屬性更方便管理。狀態可以像這樣輕鬆地變更

// a bit simpler than removing old/adding a new class
div.setAttribute('order-state', 'canceled');

但自訂屬性可能會有一個潛在的問題。如果我們為了自己的目的使用非標準屬性,而標準稍後引入了它並讓它做一些事情,會怎樣?HTML 語言是活的,它會成長,而且會出現更多屬性來滿足開發人員的需求。在這種情況下可能會產生意外的影響。

為了避免衝突,存在 data-* 屬性。

所有以「data-」開頭的屬性都保留給程式設計師使用。它們可以在 dataset 屬性中取得。

例如,如果一個 elem 有名為 "data-about" 的屬性,它可以作為 elem.dataset.about 取得。

像這樣

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

data-order-state 這樣的多字屬性會變成駝峰式大小寫:dataset.orderState

以下是重新編寫的「訂單狀態」範例

<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>
  // read
  alert(order.dataset.orderState); // new

  // modify
  order.dataset.orderState = "pending"; // (*)
</script>

使用 data-* 屬性是傳遞自訂資料的有效且安全的途徑。

請注意,我們不僅可以讀取,還可以修改資料屬性。然後 CSS 會相應地更新檢視:在上面的範例中,最後一行 (*) 將顏色變更為藍色。

摘要

  • 屬性 – 是寫在 HTML 中的內容。
  • 屬性 – 是 DOM 物件中的內容。

一個小小的比較

屬性 屬性
類型 任何值,標準屬性都有規範中描述的類型 字串
名稱 名稱區分大小寫 名稱不區分大小寫

處理屬性的方法有

  • elem.hasAttribute(name) – 檢查是否存在。
  • elem.getAttribute(name) – 取得值。
  • elem.setAttribute(name, value) – 設定值。
  • elem.removeAttribute(name) – 移除屬性。
  • elem.attributes 是所有屬性的集合。

在大部分情況下,使用 DOM 屬性較佳。我們應僅在 DOM 屬性不適合我們時才參照屬性,例如,當我們需要確切的屬性時

  • 我們需要一個非標準屬性。但如果它以 data- 開頭,則我們應使用 dataset
  • 我們想讀取 HTML 中「如寫」的值。DOM 屬性的值可能不同,例如,href 屬性永遠都是一個完整的 URL,而我們可能想要取得「原始」值。

任務

重要性:5

撰寫程式碼,從文件選取具有 data-widget-name 屬性的元素,並讀取其值。

<!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>
    // getting it
    let elem = document.querySelector('[data-widget-name]');

    // reading the value
    alert(elem.dataset.widgetName);
    // or
    alert(elem.getAttribute('data-widget-name'));
  </script>
</body>
</html>
重要性:3

透過變更其 style 屬性,將所有外部連結設為橘色。

如果連結

  • href 中有 ://
  • 但不是以 http://internal.com 開頭。

範例

<a name="list">the list</a>
<ul>
  <li><a href="http://google.com">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">ftp://ftp.com/my.zip</a></li>
  <li><a href="https://node.dev.org.tw">https://node.dev.org.tw</a></li>
  <li><a href="http://internal.com/test">http://internal.com/test</a></li>
</ul>

<script>
  // setting style for a single link
  let link = document.querySelector('a');
  link.style.color = 'orange';
</script>

結果應為

開啟一個沙盒進行任務。

首先,我們需要找出所有外部參考。

有兩種方式。

第一種是使用 document.querySelectorAll('a') 找出所有連結,然後篩選出我們需要的

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

for (let link of links) {
  let href = link.getAttribute('href');
  if (!href) continue; // no attribute

  if (!href.includes('://')) continue; // no protocol

  if (href.startsWith('http://internal.com')) continue; // internal

  link.style.color = 'orange';
}

請注意:我們使用 link.getAttribute('href')。而不是 link.href,因為我們需要 HTML 中的值。

…另一種更簡單的方式是將檢查加入 CSS 選擇器

// look for all links that have :// in href
// but href doesn't start with http://internal.com
let selector = 'a[href*="://"]:not([href^="http://internal.com"])';
let links = document.querySelectorAll(selector);

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

在沙盒中開啟解決方案。

教學課程地圖

留言

留言前先閱讀此內容…
  • 如果您有改善建議,請 提交 GitHub 問題 或提交拉取請求,而不是留言。
  • 如果您無法理解文章中的某些內容,請詳細說明。
  • 若要插入一些程式碼,請使用 <code> 標籤,若要插入多行,請將它們包在 <pre> 標籤中,若要插入超過 10 行,請使用沙盒 (plnkrjsbincodepen…)