屬性反映

一個屬性可以擴充套件一個HTMLXMLSVG或其他元素,改變其行為或提供元資料。

許多屬性會在相應的DOM介面中被反射。這意味著屬性的值可以直接在JavaScript中透過相應介面的屬性進行讀寫,反之亦然。反射屬性比使用getAttribute()setAttribute()方法來獲取和設定Element介面的屬性值,提供了更自然的程式設計方法。

本指南將概述反射屬性及其用法。

屬性getter/setter

首先,我們來看看獲取和設定屬性的預設機制,無論屬性是否被反射都可以使用它。要獲取屬性,你需要呼叫Element介面的getAttribute()方法,並指定屬性名。要設定屬性,你需要呼叫setAttribute()方法,並指定屬性名和新值。

考慮以下HTML

html
<input placeholder="Original placeholder" />

獲取和設定placeholder屬性

js
const input = document.querySelector("input");

// Get the placeholder attribute
let attr = input.getAttribute("placeholder");

// Set the placeholder attribute
input.setAttribute("placeholder", "Modified placeholder");

反射屬性

對於<input>元素,placeholder屬性會被HTMLInputElement.placeholder屬性反射。給定與前面相同的HTML

html
<input placeholder="Original placeholder" />

使用placeholder屬性可以更自然地執行相同的操作

js
const input = document.querySelector("input");

// Get the placeholder attribute
let attr = input.placeholder;

// Set the placeholder attribute
input.placeholder = "Modified placeholder";

請注意,反射屬性和屬性的名稱是相同的:placeholder。但這並非總是如此:屬性名通常遵循駝峰式命名約定。這尤其適用於多單詞的屬性名,這些屬性名包含屬性名不允許的字元,例如連字元。例如,aria-checked屬性由ariaChecked屬性反射。

布林值反射屬性

布林屬性與其他屬性略有不同,因為它們不必宣告名稱和值。例如,下面複選框<input>元素的checked屬性,在顯示時會被勾選

html
<input type="checkbox" checked />

如果輸入被勾選,Element.getAttribute()將返回"";如果未勾選,則返回null。相應的HTMLInputElement.checked屬性返回truefalse來表示勾選狀態。除此以外,布林值反射屬性與其他反射屬性相同。

列舉反射屬性

在HTML中,列舉屬性是指具有有限的、預定義的文字值集合的屬性。例如,全域性HTMLdir屬性有三個有效值:ltrrtlauto

html
<p dir="rtl">Right to left</p>

與HTML標籤名一樣,HTML列舉屬性及其值對大小寫不敏感,因此LTRRTLAUTO也是有效的。

html
<p dir="RTL">Right to left</p>

IDL反射屬性HTMLElement.dir始終返回規範中提供的規範值(在此示例中為小寫值)。設定值也會將其序列化為規範形式。

js
const pElement = document.querySelector("p");
console.log(pElement.dir); // "rtl"
pElement.dir = "RTL";
console.log(pElement.dir); // "rtl"

或者,您可以使用Element介面的getAttribute()方法。它將從HTML中獲取屬性值而不進行修改。

js
const pElement = document.querySelector("p");
console.log(pElement.getAttribute("dir")); // "RTL"

反射元素引用

注意:本節適用於包含元素引用的反射ARIA屬性。其他/未來的反射元素引用屬性很可能也適用同樣的考慮。

某些屬性以元素引用作為值:可以是元素的id值,也可以是以空格分隔的元素id值字串。這些id值引用了與其他屬性相關的其他元素,或者包含了屬性所需的其他資訊。這些屬性透過相應的屬性被反射,反射結果是一個HTMLElement派生物件例項的陣列,這些例項與id值匹配,但有一些注意事項。

例如,aria-labelledby屬性列出了包含元素可訪問名稱的元素的id值,這些名稱包含在它們的內部文字中。下面的HTML展示了這一點,針對的是一個<input>元素,該元素在一個<span>元素中定義了標籤,該span元素的id值為label_1label_2label_3

html
<span id="label_1">(Label 1 Text)</span>
<span id="label_2">(Label 2 Text)</span>
<input aria-labelledby="label_1 label_2 label_3" />

此屬性由Element.ariaLabelledByElements屬性反射,該屬性返回具有相應id值的元素陣列。屬性和相應的屬性可以按如下方式返回

js
const inputElement = document.querySelector("input");

console.log(inputElement.getAttribute("aria-labelledby"));
// "label_1 label_2 label_3"

console.log(inputElement.ariaLabelledByElements);
// [HTMLSpanElement, HTMLSpanElement]

從上面的程式碼可以注意到的第一點是,屬性和屬性包含的元素數量不同——屬性不直接反射屬性,因為引用label_3沒有對應的元素。引用也可能不匹配,因為id超出了元素的範圍。如果被引用的元素不在與該元素相同的DOM或Shadow DOM中,則可能發生這種情況,因為id僅在其宣告的範圍內有效。

我們可以迭代屬性陣列中的元素,在本例中是獲取其內部文字中的可訪問名稱(這比使用屬性更自然,因為我們不必先獲取元素引用,然後使用它們來查詢元素,而且我們只需要處理我們知道在當前範圍內可用的元素)。

js
const inputElement = document.querySelector("input");
const accessibleName = inputElement.ariaLabelledByElements
  .map((e) => e.textContent.trim())
  .join(" ");
console.log(accessibleName);
// (Label 1 Text) (Label 2 Text)

設定屬性和屬性

對於普通的反射屬性,對屬性的更新會反映在相應的屬性中,反之亦然。對於反射元素引用,情況並非如此。相反,設定屬性會清除(取消設定)屬性,因此屬性和屬性不再相互反射。例如,給定以下HTML

html
<span id="label_1">(Label 1 Text)</span>
<span id="label_2">(Label 2 Text)</span>
<input aria-labelledby="label_1 label_2" />

aria-labelledby的初始值為"label_1 label_2",但如果我們從DOM API設定它,該屬性將重置為""

js
const inputElement = document.querySelector("input");

let attributeValue = inputElement.getAttribute("aria-labelledby");
console.log(attributeValue);
// "label_1 label_2"

// Set attribute using the reflected property
inputElement.ariaLabelledByElements = document.querySelectorAll("span");

attributeValue = inputElement.getAttribute("aria-labelledby");
console.log(attributeValue);
// ""

這是有道理的,因為否則您可能會將沒有id引用的元素分配給該屬性,因此無法在屬性中表示。

設定屬性值會恢復屬性和屬性之間的關係。繼續上面的例子

js
inputElement.setAttribute("aria-labelledby", "label_1");

attributeValue = inputElement.getAttribute("aria-labelledby");
console.log(attributeValue);
// "label_1"

// Set attribute using the reflected property
console.log(inputElement.ariaLabelledByElements);
// [HTMLSpanElement] - for `label_1`

屬性返回的陣列是靜態的,因此您不能修改返回的陣列來引起相應屬性的變化。當一個數組被分配給屬性時,它會被複制,因此對屬性的任何更改都不會反映在先前返回的屬性陣列中。

元素ID引用範圍

屬性元素引用只能引用與元素位於相同DOM或Shadow DOM中的其他元素,因為元素ID僅在其宣告的範圍內有效。

我們可以在以下程式碼中看到這一點。aria-labelledby屬性的<input>元素引用了id為label_1label_2label_3的元素。然而,在這種情況下,label_3不是一個有效的id,因為它沒有在與<input>元素相同的範圍內定義。因此,標籤將僅來自id為label_1label_2的元素。

html
<div id="in_dom">
  <span id="label_3">(Label 3 Text)</span>
</div>
<div id="host">
  <template shadowrootmode="open">
    <span id="label_1">(Label 1 Text)</span>
    <input aria-labelledby="label_1 label_2 label_3" />
    <span id="label_2">(Label 2 Text)</span>
  </template>
</div>

反射元素引用範圍

使用從ARIA元素引用反射的例項屬性(例如,aria-labelledbyElement.ariaLabelledByElements)時,範圍規則略有不同。要處於範圍內,目標元素必須與引用元素位於同一DOM或父DOM中。其他DOM中的元素,包括作為引用DOM的子級或同級元素的Shadow DOM,都超出範圍。

下面的示例展示了這種情況:父DOM中的一個元素(label_3)被設定為目標,以及在同一Shadow Root中宣告的id為label_1label_2的元素。這之所以有效,是因為所有目標元素都在引用元素的範圍內。

html
<div id="in_dom">
  <span id="label_3">(Label 3 Text)</span>
</div>
<div id="host">
  <template shadowrootmode="open">
    <span id="label_1">(Label 1 Text)</span>
    <input id="input" />
    <span id="label_2">(Label 2 Text)</span>
  </template>
</div>
js
const host = document.getElementById("host");
const input = host.shadowRoot.getElementById("input");
input.ariaLabelledByElements = [
  host.shadowRoot.getElementById("label_1"),
  host.shadowRoot.getElementById("label_2"),
  document.getElementById("label_3"),
];

將DOM中的元素引用Shadow DOM中的另一個元素的等效程式碼將不起作用,因為巢狀Shadow DOM中的目標元素不在範圍內。

html
<div id="in_dom">
  <span id="label_1">(Label 1 Text)</span>
  <input id="input" />
  <span id="label_2">(Label 2 Text)</span>
</div>
<div id="host">
  <template shadowrootmode="open">
    <span id="label_3">(Label 3 Text)</span>
  </template>
</div>
js
const host = document.getElementById("host");
const input = document.getElementById("input");
input.ariaLabelledByElements = [
  host.shadowRoot.getElementById("label_3"),
  document.getElementById("label_1"),
  document.getElementById("label_2"),
];

請注意,一個元素最初可能“在範圍內”,然後被移出範圍到巢狀的Shadow Root。在這種情況下,被引用的元素仍將在屬性中列出,但不會在屬性中返回。但請注意,如果元素被移回範圍內,它將再次出現在反射的屬性中。

屬性/屬性關係總結

包含元素引用的屬性與其相應屬性之間的關係如下:

  • 屬性元素id引用僅對在與元素相同的DOM或Shadow DOM中宣告的目標元素有效
  • 反射ARIA元素引用的屬性可以定位相同範圍或父範圍內的元素。巢狀範圍內的元素是不可訪問的。
  • 設定屬性會清除屬性,並且屬性和屬性不再相互反射。如果使用Element.getAttribute()讀取屬性,則值為""
  • 使用Element.setAttribute()設定屬性也會設定屬性並恢復“反射關係”。
  • 使用稍後移出範圍的引用值設定屬性將導致從屬性陣列中移除相應的元素。但請注意,屬性仍然包含引用,如果元素被移回範圍內,屬性將再次包含該元素(即,關係已恢復)。