2022 年 10 月 14 日

搜尋:getElement*、querySelector*

當元素彼此相鄰時,DOM 導覽屬性非常棒。如果它們不相鄰呢?如何取得頁面的任意元素?

有其他搜尋方法可以做到這一點。

document.getElementById 或僅 id

如果元素有 id 屬性,我們可以使用 document.getElementById(id) 方法取得元素,無論它在哪裡。

例如

<div id="elem">
  <div id="elem-content">Element</div>
</div>

<script>
  // get the element
  let elem = document.getElementById('elem');

  // make its background red
  elem.style.background = 'red';
</script>

此外,有一個名為 id 的全域變數,它參照元素

<div id="elem">
  <div id="elem-content">Element</div>
</div>

<script>
  // elem is a reference to DOM-element with id="elem"
  elem.style.background = 'red';

  // id="elem-content" has a hyphen inside, so it can't be a variable name
  // ...but we can access it using square brackets: window['elem-content']
</script>

…除非我們宣告一個具有相同名稱的 JavaScript 變數,否則它具有優先權

<div id="elem"></div>

<script>
  let elem = 5; // now elem is 5, not a reference to <div id="elem">

  alert(elem); // 5
</script>
請勿使用 id 命名的全域變數來存取元素

此行為在 規格中說明,但主要支援相容性。

瀏覽器嘗試透過混合 JS 和 DOM 的命名空間來協助我們。這對於內嵌在 HTML 中的簡單腳本來說很好,但通常不是一件好事。可能會發生命名衝突。此外,當有人閱讀 JS 程式碼且沒有檢視 HTML 時,就不清楚變數來自何處。

在這裡的教學課程中,我們使用 id 直接參照元素以簡潔,當元素的來源很明顯時。

在實際情況中,document.getElementById 是首選方法。

id 必須是唯一的

id 必須是唯一的。文件中只能有一個具有指定 id 的元素。

如果有多個具有相同 id 的元素,則使用它的方法的行為是不可預測的,例如 document.getElementById 可能會隨機傳回任何此類元素。因此,請遵守規則並保持 id 唯一。

只有 document.getElementById,而不是 anyElem.getElementById

getElementById 方法只能在 document 物件上呼叫。它在整個文件中尋找指定的 id

querySelectorAll

到目前為止,最通用的方法是 elem.querySelectorAll(css),它會傳回 elem 內部所有符合指定 CSS 選擇器的元素。

在這裡,我們尋找所有為最後一個子項目的 <li> 元素

<ul>
  <li>The</li>
  <li>test</li>
</ul>
<ul>
  <li>has</li>
  <li>passed</li>
</ul>
<script>
  let elements = document.querySelectorAll('ul > li:last-child');

  for (let elem of elements) {
    alert(elem.innerHTML); // "test", "passed"
  }
</script>

此方法確實很強大,因為可以使用任何 CSS 選擇器。

也可以使用偽類別

CSS 選擇器中的偽類別,例如 :hover:active 也受到支援。例如,document.querySelectorAll(':hover') 會傳回目前指標所在的元素集合(依巢狀順序:從最外層的 <html> 到最內層的)。

querySelector

呼叫 elem.querySelector(css) 會傳回指定 CSS 選擇器的第一個元素。

換句話說,結果與 elem.querySelectorAll(css)[0] 相同,但後者會尋找所有元素並挑選一個,而 elem.querySelector 只會尋找一個。因此,寫起來更快速且簡潔。

matches

先前的做法是搜尋 DOM。

elem.matches(css) 並不會搜尋任何東西,它只檢查 elem 是否符合給定的 CSS 選擇器。它會傳回 truefalse

當我們在元素上進行反覆運算(例如在陣列或其他東西中)並嘗試篩選出我們感興趣的元素時,這個方法會非常方便。

例如

<a href="http://example.com/file.zip">...</a>
<a href="http://ya.ru">...</a>

<script>
  // can be any collection instead of document.body.children
  for (let elem of document.body.children) {
    if (elem.matches('a[href$="zip"]')) {
      alert("The archive reference: " + elem.href );
    }
  }
</script>

closest

元素的「祖先」是:父元素、父元素的父元素、父元素的父元素的父元素,以此類推。祖先共同組成從元素到頂端的父元素鏈。

elem.closest(css) 方法會尋找符合 CSS 選擇器的最近祖先。elem 本身也會包含在搜尋範圍內。

換句話說,closest 方法會從元素向上尋找,並檢查每個父元素。如果符合選擇器,則停止搜尋並傳回祖先。

例如

<h1>Contents</h1>

<div class="contents">
  <ul class="book">
    <li class="chapter">Chapter 1</li>
    <li class="chapter">Chapter 2</li>
  </ul>
</div>

<script>
  let chapter = document.querySelector('.chapter'); // LI

  alert(chapter.closest('.book')); // UL
  alert(chapter.closest('.contents')); // DIV

  alert(chapter.closest('h1')); // null (because h1 is not an ancestor)
</script>

getElementsBy*

還有其他方法可以透過標籤、類別等來尋找節點。

如今,這些方法大多已成為歷史,因為 querySelector 更強大且寫起來更簡短。

因此,我們在此主要為了完整性而介紹這些方法,你仍然可以在舊腳本中找到它們。

  • elem.getElementsByTagName(tag) 會尋找具有給定標籤的元素,並傳回它們的集合。tag 參數也可以是星號 "*",表示「任何標籤」。
  • elem.getElementsByClassName(className) 會傳回具有給定 CSS 類別的元素。
  • document.getElementsByName(name) 會傳回具有給定 name 屬性的元素,範圍為整個文件。很少使用。

例如

// get all divs in the document
let divs = document.getElementsByTagName('div');

讓我們在表格中找到所有 input 標籤

<table id="table">
  <tr>
    <td>Your age:</td>

    <td>
      <label>
        <input type="radio" name="age" value="young" checked> less than 18
      </label>
      <label>
        <input type="radio" name="age" value="mature"> from 18 to 50
      </label>
      <label>
        <input type="radio" name="age" value="senior"> more than 60
      </label>
    </td>
  </tr>
</table>

<script>
  let inputs = table.getElementsByTagName('input');

  for (let input of inputs) {
    alert( input.value + ': ' + input.checked );
  }
</script>
別忘了「s」這個字母!

新手開發人員有時會忘記「s」這個字母。也就是說,他們會嘗試呼叫 getElementByTagName,而不是 getElementsByTagName

"s" 這個字母在 getElementById 中不存在,因為它會傳回單一元素。但 getElementsByTagName 會傳回元素的集合,所以裡面有「s」。

它傳回一個集合,而不是一個元素!

另一個常見的新手錯誤是寫成

// doesn't work
document.getElementsByTagName('input').value = 5;

這不會奏效,因為它會取得輸入的集合,並將值指定給它,而不是指定給其中的元素。

我們應該反覆運算集合或透過索引取得元素,然後再指定,如下所示

// should work (if there's an input)
document.getElementsByTagName('input')[0].value = 5;

尋找 .article 元素

<form name="my-form">
  <div class="article">Article</div>
  <div class="long article">Long article</div>
</form>

<script>
  // find by name attribute
  let form = document.getElementsByName('my-form')[0];

  // find by class inside the form
  let articles = form.getElementsByClassName('article');
  alert(articles.length); // 2, found two elements with class "article"
</script>

動態集合

所有 "getElementsBy*" 方法都會傳回一個動態集合。此類集合總是反映文件的當前狀態,並在文件變更時「自動更新」。

在以下範例中,有兩個腳本。

  1. 第一個腳本會建立一個對 <div> 集合的參考。目前,它的長度為 1
  2. 第二個腳本會在瀏覽器遇到另一個 <div> 後執行,因此它的長度為 2
<div>First div</div>

<script>
  let divs = document.getElementsByTagName('div');
  alert(divs.length); // 1
</script>

<div>Second div</div>

<script>
  alert(divs.length); // 2
</script>

相反地,querySelectorAll 會傳回一個靜態集合。它就像一個固定的元素陣列。

如果我們改用它,則兩個腳本都會輸出 1

<div>First div</div>

<script>
  let divs = document.querySelectorAll('div');
  alert(divs.length); // 1
</script>

<div>Second div</div>

<script>
  alert(divs.length); // 1
</script>

現在我們可以輕鬆地看出差異。在文件出現新的 div 後,靜態集合並未增加。

摘要

在 DOM 中搜尋節點有 6 種主要方法

方法 依據...搜尋 可以在元素上呼叫嗎? 即時嗎?
querySelector CSS 選擇器 -
querySelectorAll CSS 選擇器 -
getElementById id - -
getElementsByName 名稱 -
getElementsByTagName 標籤或 '*'
getElementsByClassName 類別

到目前為止,使用最廣泛的是 querySelectorquerySelectorAll,但 getElement(s)By* 可能偶爾有用或在舊腳本中找到。

除此之外

  • elem.matches(css) 檢查 elem 是否符合指定的 CSS 選擇器。
  • elem.closest(css) 尋找符合指定 CSS 選擇器的最近祖先。elem 本身也會受到檢查。

讓我們在此再提到一種檢查親子關係的方法,因為它有時很有用

  • elemA.contains(elemB) 如果 elemBelemA 內部(elemA 的後代)或當 elemA==elemB 時,則傳回 true。

作業

重要性:4

以下是包含表格和表單的文件。

如何尋找?…

  1. 具有 id="age-table" 的表格。
  2. 該表格內的所有 label 元素(應該有 3 個)。
  3. 該表格中的第一個 td(帶有單字「年齡」)。
  4. 具有 name="search"form
  5. 該表單中的第一個 input
  6. 該表單中的最後一個 input

在一個獨立的視窗中開啟頁面 table.html,並使用瀏覽器工具進行操作。

有許多方法可以做到這一點。

以下是其中一些方法

// 1. The table with `id="age-table"`.
let table = document.getElementById('age-table')

// 2. All label elements inside that table
table.getElementsByTagName('label')
// or
document.querySelectorAll('#age-table label')

// 3. The first td in that table (with the word "Age")
table.rows[0].cells[0]
// or
table.getElementsByTagName('td')[0]
// or
table.querySelector('td')

// 4. The form with the name "search"
// assuming there's only one element with name="search" in the document
let form = document.getElementsByName('search')[0]
// or, form specifically
document.querySelector('form[name="search"]')

// 5. The first input in that form.
form.getElementsByTagName('input')[0]
// or
form.querySelector('input')

// 6. The last input in that form
let inputs = form.querySelectorAll('input') // find all inputs
inputs[inputs.length-1] // take the last one
教學課程地圖

留言

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