Властивості навігації по DOM чудові, коли елементи розташовані близько один до одного. А якщо ні? Як отримати довільний елемент сторінки?
Для цього в DOM існують додаткові методи пошуку.
document.getElementById або просто id
Якщо елемент має атрибут id
, ми можемо отримати його викликом document.getElementById(id)
, незалежно від того, де він знаходиться.
Наприклад:
<div id="elem">
<div id="elem-content">Елемент</div>
</div>
<script>
// отримати елемент
let elem = document.getElementById('elem');
// зробити його фон червоним
elem.style.background = 'red';
</script>
Крім того, існує глобальна змінна з назвою id
, яка посилається на елемент:
<div id="elem">
<div id="elem-content">Елемент</div>
</div>
<script>
// elem -- це посилання на елемент DOM з id="elem"
elem.style.background = 'red';
// id="elem-content" містить дефіс всередині, тому не може бути ім’ям змінної
// ...але ми можемо отримати доступ до нього за допомогою квадратних дужок: window['elem-content']
</script>
…Але це лише якщо ми не оголосили змінну JavaScript з таким же ім’ям, інакше вона матиме пріоритет:
<div id="elem"></div>
<script>
let elem = 5; // тепер elem дорівнює 5, а не посилається на <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>Цей</li>
<li>тест</li>
</ul>
<ul>
<li>повністю</li>
<li>пройдено</li>
</ul>
<script>
let elements = document.querySelectorAll('ul > li:last-child');
for (let elem of elements) {
alert(elem.innerHTML); // "тест", "пройдено"
}
</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>
// може бути будь-якою колекцією замість document.body.children
for (let elem of document.body.children) {
if (elem.matches('a[href$="zip"]')) {
alert("Посилання на архів: " + elem.href );
}
}
</script>
closest
Предками елемента є: батько, батько батька, його батько і так далі. Предки разом утворюють ланцюг батьків від елемента до вершини.
Метод elem.closest(css)
шукає найближчого предка, який відповідає CSS-селектору. Сам elem
також включається в пошук.
Іншими словами, метод closest
йде вгору від елемента і перевіряє кожного з батьківських елементів. Якщо він відповідає заданому селектору, пошук припиняється, і повертається знайдений предок.
Наприклад:
<h1>Зміст</h1>
<div class="contents">
<ul class="book">
<li class="chapter">Розділ 1</li>
<li class="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 (тому що h1 -- не предок)
</script>
getElementsBy*
Існують також інші методи пошуку елементів за тегом, класом тощо.
Сьогодні вони здебільшого історичні, оскільки querySelector
є потужнішим і коротшим для написання.
Тому тут ми розглянемо їх переважно для повноти, тоді як ви все ще можете знайти їх у старих скриптах.
elem.getElementsByTagName(tag)
шукає елементи з заданим тегом і повертає колекцію цих елементів. Параметрtag
також може бути зірочкою"*"
для “будь-яких тегів”.elem.getElementsByClassName(className)
повертає елементи, які мають вказаний CSS-клас.document.getElementsByName(name)
повертає елементи з заданим атрибутомname
для всього документа. Використовується дуже рідко.
Наприклад:
// отримує всі елементи div у документі
let divs = document.getElementsByTagName('div');
Знайдімо всі теги input
в таблиці:
<table id="table">
<tr>
<td>Ваш вік:</td>
<td>
<label>
<input type="radio" name="age" value="young" checked> молодше 18
</label>
<label>
<input type="radio" name="age" value="mature"> від 18 до 50
</label>
<label>
<input type="radio" name="age" value="senior"> старше 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"
.
Іншою поширеною помилкою новачків є ось таке написання:
// не працює
document.getElementsByTagName('input').value = 5;
Це не спрацює, тому що код приймає колекцію вхідних даних і призначає значення їй, а не елементам всередині неї.
Ми повинні або перебрати колекцію, або отримати елемент за його індексом, а потім призначити, як тут:
// має працювати (якщо є input)
document.getElementsByTagName('input')[0].value = 5;
Шукаємо елементи .article
:
<form name="my-form">
<div class="article">Стаття</div>
<div class="long article">Довга стаття</div>
</form>
<script>
// шукаємо за ім’ям атрибуту
let form = document.getElementsByName('my-form')[0];
// шукаємо за класом всередині form
let articles = form.getElementsByClassName('article');
alert(articles.length); // 2, знаходимо два елементи з класом "article"
</script>
Живі колекції
Усі методи "getElementsBy*"
повертають живу колекцію. Такі колекції завжди відображають поточний стан документа і “автооновлюються” при його зміні.
У нижченаведеному прикладі є два скрипта.
- Перший створює посилання на колекцію
<div>
. На даний момент його довжина дорівнює1
. - Другий скрипт запускається після того, як браузер зустріне ще один
<div>
, тому його довжина дорівнює2
.
<div>Перший div</div>
<script>
let divs = document.getElementsByTagName('div');
alert(divs.length); // 1
</script>
<div>Другий div</div>
<script>
alert(divs.length); // 2
</script>
На відміну від цього, querySelectorAll
повертає статичну колекцію. Це схоже на фіксований масив елементів.
Якщо ми використаємо його у вищенаведеному прикладі, тоді обидва сценарії виведуть 1
:
<div>Перший div</div>
<script>
let divs = document.querySelectorAll('div');
alert(divs.length); // 1
</script>
<div>Другий div</div>
<script>
alert(divs.length); // 1
</script>
Тепер ми легко бачимо різницю. Статична колекція не збільшилася після появи нового div
у документі.
Підсумки
Існує 6 основних методів пошуку елементів у DOM:
Метод | Шукає, використовуючи... | Чи може викликатися на елементі? | Чи повертає живу колекцію? |
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)
повертає true, якщоelemB
знаходиться всерединіelemA
(нащадокelemA
) або колиelemA==elemB
.