當元素彼此相鄰時,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>
此行為在 規格中說明,但主要支援相容性。
瀏覽器嘗試透過混合 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 選擇器。它會傳回 true
或 false
。
當我們在元素上進行反覆運算(例如在陣列或其他東西中)並嘗試篩選出我們感興趣的元素時,這個方法會非常方便。
例如
<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」這個字母。也就是說,他們會嘗試呼叫 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*"
方法都會傳回一個動態集合。此類集合總是反映文件的當前狀態,並在文件變更時「自動更新」。
在以下範例中,有兩個腳本。
- 第一個腳本會建立一個對
<div>
集合的參考。目前,它的長度為1
。 - 第二個腳本會在瀏覽器遇到另一個
<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 |
類別 | ✔ | ✔ |
到目前為止,使用最廣泛的是 querySelector
和 querySelectorAll
,但 getElement(s)By*
可能偶爾有用或在舊腳本中找到。
除此之外
- 有
elem.matches(css)
檢查elem
是否符合指定的 CSS 選擇器。 - 有
elem.closest(css)
尋找符合指定 CSS 選擇器的最近祖先。elem
本身也會受到檢查。
讓我們在此再提到一種檢查親子關係的方法,因為它有時很有用
elemA.contains(elemB)
如果elemB
在elemA
內部(elemA
的後代)或當elemA==elemB
時,則傳回 true。
留言
<code>
標籤,若要插入多行程式碼,請將它們包在<pre>
標籤中,若要插入超過 10 行程式碼,請使用沙盒 (plnkr、jsbin、codepen…)