Как найти элемент в dom js

В этой статье мы изучим методы JavaScript для поиска элементов в HTML-документе: querySelector, querySelectorAll, getElementById и другие. Кроме них рассмотрим ещё следующие: matches, contains и closest. Первые два из них могут быть полезны для выполнения различных проверок, а третий использоваться, когда нужно получить родительский элемент по CSS-селектору.

Методы для выбора HTML-элементов

Работа с веб-страницей так или иначе связана с манипулированием HTML-элементами. Но перед тем, как над ними выполнить некоторые действия (например, добавить стили), их сначала нужно получить.

Выбор элементов в основном выполняется с помощью этих методов:

  • querySelector;
  • querySelectorAll.

Они позволяют выполнить поиск HTML-элементов по CSS-селектору. При этом querySelector выбирает один элемент, а querySelectorAll – все.

Кроме них имеются ещё:

  • getElementById;
  • getElementsByClassName;
  • getElementsByTagName;
  • getElementsByName.

Но они сейчас применяются довольно редко. В основном используется либо querySelector, либо querySelectorAll.

querySelectorAll

Метод querySelectorAll применяется для выбора всех HTML-элементов, подходящих под указанный CSS-селектор. Он позволяет искать элементы как по всей странице, так и внутри определённого элемента:

// выберем элементы по классу item во всем документе
const items = document.querySelectorAll('.item');
// выберем .btn внутри #slider
const buttons = document.querySelector('#slider').querySelectorAll('.btn');

Здесь на первой строчке мы нашли все элементы с классом item. На следующей строчке мы сначала выбрали элемент с id="slider", а затем в нём все HTML-элементы с классом btn.

Метод querySelectorAll как вы уже догадались принимает в качестве аргумента CSS-селектор в формате строки, который соответственно и определяет искомые элементы. В качестве результата querySelectorAll возвращает объект класса NodeList. Он содержит все найденные элементы:

Выбор HTML-элементов с помощью метода querySelectorAll в JavaScript

Полученный набор представляет собой статическую коллекцию HTML-элементов. Статической она называется потому, что она не изменяется. Например, вы удалили элемент из HTML-документа, а в ней как был этот элемент, так он и остался. Чтобы обновить набор, querySelectorAll нужно вызвать заново:

Статический набор HTML-элементов, выбранный с помощью querySelectorAll в JavaScript

Узнать количество найденных элементов можно с помощью свойства length:

// выберем элементы с атрибутом type="submit"
const submits = document.querySelectorAll('[type="submit"]');
// получим количество найденных элементов
const countSubmits = submits.length;

Обращение к определённому HTML-элементу коллекции выполняется также как к элементу массива, то есть по индексу. Индексы начинаются с 0:

// получим первый элемент
const elFirst = submits[0];
// получим второй элемент
const elSecond = submits[1];

Здесь в качестве результата мы получаем HTML-элемент или undefined, если элемента с таким индексом в наборе NodeList нет.

Перебор коллекции HTML-элементов

Перебор NodeList обычно осуществляется с помощью forEach:

// получим все <p> на странице
const elsP = document.querySelectorAll('p');
// переберём выбранные элементы
elsP.forEach((el) => {
  // установим каждому элементу background-color="yellow"
  el.style.backgroundColor = 'yellow';
});

Также перебрать набор выбранных элементов можно с помощью цикла for или for...of:

// получим все элементы p на странице
const elsP = document.querySelectorAll('p');
// for
for (let i = 0, length = elsP.length; i < length; i++) {
  elsP[i].style.backgroundColor = 'yellow';
}
// for...of
for (let el of elsP) {
  el.style.backgroundColor = 'yellow';
}

querySelector

Метод querySelector также как и querySelectorAll выполняет поиск по CSS-селектору. Но в отличие от него, он ищет только один HTML-элемент:

// ищем #title во всём документе
const elTitle = document.querySelector('#title');
// ищем footer в <body>
const elFooter = document.body.querySelector('footer');

На первой строчке мы выбираем HTML-элемент, имеющий в качестве id значение title. На второй мы ищем в <body> HTML-элемент по тегу footer.

В качестве результата этот метод возвращает найденный HTML-элемент или null, если он не был найден.

querySelector всегда возвращает один HTML-элемент, даже если под указанный CSS-селектор подходят несколько:

<ul id="list">
  <li>First</li>
  <li>Second</li>
  <li>Third</li>
</ul>
<script>
  // выберем <li>, расположенный в #list
  const elFirst = document.querySelector('#list > li');
  elFirst.style.backgroundColor = 'yellow';
</script>

Задачу, которую решает querySelector можно выполнить через querySelectorAll:

const elFirst = document.querySelectorAll('#list > li')[0];

Но querySelector в отличие от querySelectorAll делает это намного быстрее, да и писать так проще. То есть querySelectorAll не возвращает как querySelector сразу же первый найденный элемент. Он сначала ищет все элементы, и только после того, как он это сделает, мы можем уже обратиться к первому HTML-элементу в этой коллекции.

Обычно перед тем, как выполнить какие-то действия с найденным HTML-элементом необходимо сначала проверить, а действительно ли он был найден:

const elModal = document.querySelector('.modal');
// если элемент .modal найден, то ...
if (elModal) {
  // переключим у elModal класс show
  elModal.classList.toggle('show');
}

Здесь мы сначала проверили существования HTML-элемента, и только потом выполнили над ним некоторые действия.

Методы getElement(s)By* для выбора HTML-элементов

Здесь мы рассмотрим методы, которые сейчас применяются довольно редко для поиска HTML-элементов. Но в некоторых случаях они могут быть очень полезны. Это:

  • getElementById – получает один элемент по id;
  • getElementsByClassName – позволяет найти все элементы с указанным классом или классами;
  • getElementsByTagName – выбирает элементы по тегу;
  • getElementsByName – получает все элементы с указанным значением атрибута name.

1. Метод getElementById позволяет найти HTML-элемент на странице по значению id:

<div id="comments">...</div>
...
<script>
  // получим HTMLElement и сохраним его в переменную elComments
  const elComments = document.getElementById('comments');
</script>

В качестве результата getElementById возвращает объект класса HTMLElement или значение null, если элемент не был найден. Этот метод имеется только у объекта document.

Указывать значение id необходимо с учётом регистра. Так например, document.getElementById('aside') и document.getElementById('ASIDE') ищут элементы с разным id.

Обратите внимание, что в соответствии со стандартом в документе не может быть несколько тегов с одинаковым id, так как значение идентификатора на странице должно быть уникальным.

Тем не менее, если вы допустили ошибку и в документе существуют несколько элементов с одинаковым id, то метод getElementById более вероятно вернёт первый элемент, который он встретит в DOM. Но на это полагаться нельзя, так как такое поведение не прописано в стандарте.

То, что делает getElementById можно очень просто решить посредством querySelector:

// получим элемент #title
const elTitle = document.getElementById('title');
// получим элемента #title, используя querySelector
const elTitleSame = document.querySelector('#nav');

Кстати, оба этих метода возвращают в качестве результата один и тот же результат. Это либо HTML-элемент (экземпляр класса HTMLElement) или null, если элемент не найден.

2. Метод getElementsByClassName позволяет найти все элементы с заданным классом или классами. Его можно применить для поиска элементов как во всём документе, так и внутри указанного. В первом случае его нужно будет вызывать как метод объекта document, а во втором – как метод соответствующего HTML-элемента:

// найдем элементы с классом control в документе
const elsControl = document.getElementsByClassName('control');
// выберем элементы внутри другого элемента, в данном случае внутри формы с id="myform"
const elsFormControl = document.forms.myform.getElementsByClassName('form-control');

В качестве результата он возвращает живую HTML-коллекцию найденных элементов. Чем живая коллекция отличается от статической мы рассмотрим ниже.

Здесь мы сохранили найденные элементы в переменные elsControl и elsFormControl. В первой переменной будет находиться HTMLCollection, содержащая элементы с классом control. Во второй – набор элементов с классом form-control, находящиеся в форме с id="myform". Для получения этой формы мы использовали document.forms.myform.

Метод getElementsByClassName позволяет искать элементы не только по одному классу, но и сразу по нескольким, которые должны присутствовать у элемента:

// выберем элементы .btn.btn-danger
const elsBtn = document.getElementsByClassName('btn btn-danger');

На querySelectorAll этот пример решается так:

const elsBtn = document.querySelectorAll('.btn.btn-danger');

3. Метод getElementsByTagName предназначен для получения коллекции элементов по имени тега:

// найдем все <a> в документе
const anchors = document.getElementsByTagName('a');
// найдем все >li> внутри #list
const elsLi = document.getElementById('list').getElementsByTagName('li');

На первой строчке мы выбрали все <a> в документе и присвоили полученную HTMLCollection переменной anchors. На второй – мы сначала получили #list, а затем в нём нашли все <li>.

Задачу по выбору элементов внутри другого элемента с помощью querySelectorAll выполняется намного проще:

const elsLi = document.querySelectorAll('#list li');

Для выбора всех элементов можно использовать символ *:

// выберем все элементы в <body>
const els = document.body.getElementsByTagName('*');

4. В JavaScript getElementsByName можно использовать для выбора элементов, имеющих определенное значение атрибута name:

// получим все элементы с name="phone"
const elsPhone = document.getElementsByName('phone');

Через querySelectorAll это выполняется так:

const elsPhone = document.querySelectorAll('[name="phone"]');

getElementsBy* и живые HTML-коллекции

В JavaScript getElementsByTagName, getElementsByClassName и getElementsByName в отличие от других методов (например, querySelectorAll) возвращают живую коллекцию HTML-элементов (на английском live HTMLCollection). То есть коллекцию содержимое которой автоматически обновляется при изменении DOM. Для наглядности рассмотрим следующий пример.

Например, на странице изначально имеется два <li>. Выберем их с помощью getElementsByTagName и сохраним полученную HTMLCollection в переменную els. Теперь с помощью els мы можем получить эту коллекцию. Сейчас в ней два <li>. Затем через 5 секунд, используя setTimeout добавим ещё один <li>. Если сейчас мы обратимся к переменной els, то увидим, что в ней уже находятся три <li>:

<ul>
  <li>One</li>
  <li>Two</li>
</ul>

<script>
// получим живую коллекцию <li>
const els = document.getElementsByTagName('li');
// выведем количество <li> в консоль
console.log(`Количество <li>: ${els.length}`); // 2
// через 5 секунд добавим ещё один <li>
setTimeout(() => {
  // вставим на страницу новый <li>
  document.querySelector('ul').insertAdjacentHTML('beforeend', '<li>Three</li>');
// выведем количество <li> в консоль
  console.log(`Количество <li>: ${els.length}`); // 3
}, 5000);
</script>

Живая коллекция элементов, полученная с помощью getElementsByTagName, обновляется при изменении DOM

Как вы видите, здесь полученная коллекция является живой, то есть она может автоматически измениться. В ней сначала было два <li>. Но после того, как мы на страницу добавили ещё один подходящий элемент, в ней их стало уже три.

Если в коде приведённом выше заменить выбор элементов на querySelectorAll, то мы увидим, что в ней находится статическая (не живая) коллекция элементов:

// получим статическую коллекцию
const els = document.querySelectorAll('li'); <li>

Статическая коллекция элементов, полученная с помощью querySelectorAll, не обновляется при изменении DOM

Как вы видите количество элементов в коллекции не изменилось. Чтобы после изменения DOM получить актуальную коллекцию элементов, их нужно просто выбрать заново посредством querySelectorAll:

<ul>
  <li>One</li>
  <li>Two</li>
</ul>

<script>
// получим статическую коллекцию <li>
let els = document.querySelectorAll('li');
// выведем количество <li> в консоль
console.log(`Количество <li>: ${els.length}`); // 2
// через 5 секунд добавим ещё один <li>
setTimeout(() => {
  // вставим на страницу новый <li>
  document.querySelector('ul').insertAdjacentHTML('beforeend', '<li>Three</li>');
  // получим заново статическую коллекцию <li>
  els = document.querySelectorAll('li');
  // выведем количество <li> в консоль
  console.log(`Количество <li>: ${els.length}`); // 3
}, 5000);
</script>

Таким образом в JavaScript насчитывается 6 основных методов для выбора HTML-элементов на странице. По чему они ищут и что они возвращают приведено на следующем рисунке:

Методы JavaScript для выбора HTML-элементов на странице, приведены сведения по признаку на основании которого они ищут и тому что возвращают в качестве результата

Экземпляры класса HTMLCollection не имеют в прототипе метод forEach. Поэтому если вы хотите использовать этот метод для перебора такой коллекции, её необходимо преобразовать в массив:

const items = document.getElementsByClassName('item');
[...items].forEach((el) => {
  console.log(el);
});

matches, closest и contains

В JavaScript имеются очень полезные методы:

  • matches – позволяет проверить соответствует ли HTML-элемент указанному CSS-селектору;
  • closest – позволяет найти для HTML-элемента его ближайшего предка, подходящего под указанный CSS-селектор (поиск начинается с самого элемента);
  • contains – позволяет проверить содержит ли данный узел другой в качестве потомка (проверка начинается с самого этого узла).

1. Метод matches ничего не выбирает, но он является очень полезным, так как позволяет проверить HTML-элемент на соответствие CSS-селектору. Он возвращает true, если элемент ему соответствует, иначе false.

// выберем HTML элемент, имеющий атрибут data-target="slider"
const elSlider = document.querySelector('[data-target="slider"]');
// проверим соответствует ли он CSS селектору 'div'
const result = element.matches('div');

Пример, в котором выберем все <li>, расположенные внутри #questions, а затем удалим те из них, которые соответствуют селектору .answered:

// выберем все <li> в #questions
const els = document.querySelectorAll('#questions > li');
// переберём выбранные элементы
els.forEach((el) => {
  // если элемент соответствует селектору .answered, то ...
  if (el.matches('.answered')) {
    // удалим элемент
    el.remove();
  }
});

В этом примере проверим каждый <li> на соответствие селектору active. Выведем в консоль каждый такой элемент:

<ul>
  <li>One</li>
  <li class="active">Two</li>
  <li>Three</li>
</ul>

<script>
  document.querySelectorAll('li').forEach((el) => {
    if (el.matches('.active')) {
      console.log(el);
    }
  });
  // li.active
</script>

Ранее, в «старых» браузерах данный метод имел название matchesSelector, а также поддерживался с использованием префиксов. Если вам нужна поддержка таких браузеров, то можно использовать следующий полифилл:

if (!Element.prototype.matches) {
  Element.prototype.matches = Element.prototype.matchesSelector || Element.prototype.webkitMatchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector;
}

2. Метод closest очень часто используется в коде. Он позволяет найти ближайшего предка, подходящего под указанный CSS-селектор. При этом поиск начинается с самого элемента, для которого данный метод вызывается. Если этот элемент будет ему соответствовать, то closest вернёт его.

<div class="level-1">
  <div class="level-2">
    <div class="level-3"></div>
  </div>
</div>

<script>
  const el = document.querySelector('.level-3');
  const elAncestor = el.closest('.level-1');
  console.log(elAncestor);
</script>

Здесь мы сначала выбираем HTML-элемент .level-3 и присваиваем его переменной el. Далее мы пытаемся среди предков этого элемента включая его сам найти такой, который отвечает заданному CSS-селектору, в данном случае .level-1.

Начинается поиск всегда с самого этого элемента. В данном случае он не подходит под указанный селектор. Следовательно, этот метод переходит к его родителю. Он тоже не отвечает этому CSS-селектору. Значит, closest переходит дальше, то есть уже к его родителю. Этот элемент подходит под указанный селектор. Поэтому поиск прекращается и этот метод возвращает его в качестве результата.

Метод closest возвращает null, когда он дошёл бы конца иерархии и не нашёл элемент отвечающий указанному селектору. То есть, если такого элемента нет среди предков.

В этом примере найдем с помощью closest для .active его ближайшего родителя, отвечающего CSS-селектору #list > li:

<ul id="list">
  <li>One</li>
  <li>
    Two
    <ul>
      <li>Four</li>
      <li class="active">Five</li>
    </ul>
  </li>
  <li>Three</li>
</ul>
<script>
  const elActive = document.querySelector('.active');
  const elClosest = elActive.closest('#list > li');
  elClosest.style.backgroundColor = 'yellow';
</script>

В JavaScript closest очень часто используется в обработчиках событий. Это связано с тем, чтобы события всплывают и нам нужно, например, узнать кликнул ли пользователь в рамках какого-то элемента:

document.addEventListener('click', (e) => {
  if (e.closest.matches('.btn__action')) {
    // пользователь кликнул внутри .btn__action
  }
});

3. Метод contains позволяет проверить содержит ли некоторый узел другой в качестве потомка. При этом проверка начинается с самого этого узла, для которого этот метод вызывается. Если узел соответствует тому для которого мы вызываем данный метод или является его потомком, то contains в качестве результата возвращает логическое значение true. В противном случае false:

<div id="div-1">
  <div id="div-2">
    <div id="div-3">...</div>
  </div>
</div>
<div id="div-4">...</div>

<script>
  const elDiv1 = document.querySelector('#div-1');
  elDiv1.contains(elDiv1); // true
  const elDiv3 = document.querySelector('#div-3');
  elDiv1.contains(elDiv3); // true
  const elDiv4 = document.querySelector('#div-4');
  elDiv1.contains(elDiv4); // false
</script>

Здесь выражение elDiv1.contains(elDiv1) возвращает true, так как проверка начинается с самого элемента. Это выражение elDiv1.contains(elDiv3) тоже возвращает true, так как elDiv3 находится внутри elDiv1. А вот elDiv1.contains(elDiv4) в качестве результата возвращает false, так как elDiv4 не находится внутри elDiv1.

В этом примере проверим с помощью contains содержит ли <p> другие узлы в качестве потомка:

<h1>Tag b</h1>
<p>This is <b>tag b</b>.</p>

<script>
  const elP = document.querySelector('p');
  const elB = document.querySelector('b');
  const textNode = elB.firstChild;
  const elH1 = document.querySelector('h1');
  elP.contains(elP); // true
  elP.contains(elB); // true
  elP.contains(elH1); // false
  elP.contains(textNode); // true
</script>

Метод contains позволяет проверить является ли потомком не только узел-элемент, но и любой другой узел. Например, узнаем является ли потомком elDiv1 указанный текстовый узел:

const elDiv1 = document.querySelector('#div-1');
const textNode = document.querySelector('#div-3').firstChild;
elDiv1.contains(textNode); // true

Задачи

1. Узнать количество элементов с атрибутом data-toggle="modal" на странице:

const count = document.querySelectorAll('[data-toggle="modal"]').length;
console.log(count);

2. Найти все элементы <a> с классом nav внутри элемента <ul> :

const anchors = document.querySelectorAll('ul.nav a');

3. Получить элемент по id, значение которого равно pagetitle:

var pagetitle = document.querySelector('#pagetitle');

4. Выполнить поиск элемента по классу nav:

var el = document.querySelector('.nav');

5. Найти элемент <h3>, находящийся в теге <div> с классом comments, который в свою очередь расположен в <main>:

var header = document.querySelector('main div.comments h3');

6. Имеется страница. В ней следует выбрать:

  • последний элемент с классом article, расположенный в <main> (решение);
  • все элементы .section, находящиеся в .aside кроме 2 второго (решение);
  • элемент <nav> расположенный после <header> (решение).

Поиск: getElement*, querySelector*

Свойства навигации по DOM хороши, когда элементы расположены рядом. А что, если нет? Как получить произвольный элемент страницы?

Для этого в DOM есть дополнительные методы поиска.

document.getElementById или просто id

Если у элемента есть атрибут id, то мы можем получить его вызовом document.getElementById(id), где бы он ни находился.

Например:

<div id="elem">
  <div id="elem-content">Element</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 - ссылка на элемент с id="elem"
  elem.style.background = 'red';

  // внутри id="elem-content" есть дефис, так что такой id не может служить именем переменной
  // ...но мы можем обратиться к нему через квадратные скобки: window['elem-content']
</script>

…Но это только если мы не объявили в JavaScript переменную с таким же именем, иначе она будет иметь приоритет:

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

<script>
  let elem = 5; // теперь elem равен 5, а не <div id="elem">

  alert(elem); // 5
</script>
Это поведение соответствует [стандарту](https://html.spec.whatwg.org/#dom-window-nameditem), но поддерживается в основном для совместимости, как осколок далёкого прошлого.

Браузер пытается помочь нам, смешивая пространства имён JS и DOM. Это удобно для простых скриптов, которые находятся прямо в HTML, но, вообще говоря, не очень хорошо. Возможны конфликты имён. Кроме того, при чтении JS-кода, не видя HTML, непонятно, откуда берётся переменная.

В этом учебнике мы будем обращаться к элементам по `id` в примерах для краткости, когда очевидно, откуда берётся элемент.

В реальной жизни лучше использовать `document.getElementById`.

“`smart header=”Значение id должно быть уникальным”
Значение `id` должно быть уникальным. В документе может быть только один элемент с данным `id`.

Если в документе есть несколько элементов с одинаковым значением id, то поведение методов поиска непредсказуемо. Браузер может вернуть любой из них случайным образом. Поэтому, пожалуйста, придерживайтесь правила сохранения уникальности id.


```warn header="Только `document.getElementById`, а не `anyElem.getElementById`"
Метод `getElementById` можно вызвать только для объекта `document`. Он осуществляет поиск по `id` по всему документу.

querySelectorAll [#querySelectorAll]

Самый универсальный метод поиска – это elem.querySelectorAll(css), он возвращает все элементы внутри elem, удовлетворяющие данному CSS-селектору.

Следующий запрос получает все элементы <li>, которые являются последними потомками в <ul>:

<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')` вернёт коллекцию (в порядке вложенности: от внешнего к внутреннему) из текущих элементов под курсором мыши.

querySelector [#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-селектору. Сам элемент также включается в поиск.

Другими словами, метод closest поднимается вверх от элемента и проверяет каждого из родителей. Если он соответствует селектору, поиск прекращается. Метод возвращает либо предка, либо null, если такой элемент не найден.

Например:

<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) ищет элементы с данным тегом и возвращает их коллекцию. Передав "*" вместо тега, можно получить всех потомков.
  • 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>

“`warn header=”Не забываем про букву "s"!”
Одна из самых частых ошибок начинающих разработчиков (впрочем, иногда и не только) – это забыть букву `”s”`. То есть пробовать вызывать метод `getElementByTagName` вместо getElementsByTagName.

Буква "s" отсутствует в названии метода getElementById, так как в данном случае возвращает один элемент. Но getElementsByTagName вернёт список элементов, поэтому "s" обязательна.


````warn header="Возвращает коллекцию, а не элемент!"
Другая распространённая ошибка - написать:

```js
// не работает
document.getElementsByTagName('input').value = 5;

Попытка присвоить значение коллекции, а не элементам внутри неё, не сработает.

Нужно перебрать коллекцию в цикле или получить элемент по номеру и уже ему присваивать значение, например, так:

// работает (если есть input)
document.getElementsByTagName('input')[0].value = 5;

Ищем элементы с классом `.article`:

```html run height=50
<form name="my-form">
  <div class="article">Article</div>
  <div class="long article">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*"` возвращают *живую* коллекцию. Такие коллекции всегда отражают текущее состояние документа и автоматически обновляются при его изменении.

В приведённом ниже примере есть два скрипта.

1. Первый создаёт ссылку на коллекцию `<div>`. На этот момент её длина равна `1`.
2. Второй скрипт запускается после того, как браузер встречает ещё один `<div>`, теперь её длина - `2`.

```html run
<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`:


```html run
<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` в документе.

## Итого

Есть 6 основных методов поиска элементов в DOM:

<table>
<thead>
<tr>
<td>Метод</td>
<td>Ищет по...</td>
<td>Ищет внутри элемента?</td>
<td>Возвращает живую коллекцию?</td>
</tr>
</thead>
<tbody>
<tr>
<td><code>querySelector</code></td>
<td>CSS-selector</td>
<td>✔</td>
<td>-</td>
</tr>
<tr>
<td><code>querySelectorAll</code></td>
<td>CSS-selector</td>
<td>✔</td>
<td>-</td>
</tr>
<tr>
<td><code>getElementById</code></td>
<td><code>id</code></td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td><code>getElementsByName</code></td>
<td><code>name</code></td>
<td>-</td>
<td>✔</td>
</tr>
<tr>
<td><code>getElementsByTagName</code></td>
<td>tag or <code>'*'</code></td>
<td>✔</td>
<td>✔</td>
</tr>
<tr>
<td><code>getElementsByClassName</code></td>
<td>class</td>
<td>✔</td>
<td>✔</td>
</tr>
</tbody>
</table>

Безусловно, наиболее часто используемыми в настоящее время являются методы `querySelector` и `querySelectorAll`, но и методы  `getElement(s)By*` могут быть полезны в отдельных случаях, а также встречаются в старом коде.

Кроме того:

- Есть метод `elem.matches(css)`, который проверяет, удовлетворяет ли элемент CSS-селектору.
- Метод `elem.closest(css)` ищет ближайшего по иерархии предка, соответствующему данному CSS-селектору. Сам элемент также включён в поиск.

И, напоследок, давайте упомянем ещё один метод, который проверяет наличие отношений между предком и потомком:
-  `elemA.contains(elemB)` вернёт `true`, если `elemB` находится внутри `elemA` (`elemB` потомок `elemA`) или когда `elemA==elemB`.

На этом занятии
рассмотрим методы поиска произвольных элементов в HTML-документе. Рассмотренные
свойства на предыдущем занятии по навигации DOM-дереву хороши
если элементы расположены рядом. Но что делать, если требуемый объект может
находиться в самых разных местах. Как его искать? Вот об этом и пойдет сейчас
речь.

В самом простом
случае мы можем у любого тега HTML-документа прописать атрибут id с некоторым уникальным
значением. Например:

<!DOCTYPE html>
<html>
<head>
    <title>Уроки по JavaScript</title>
</head>
<body>
<div id="div_id">
    <p>Текст внутри блока div
</div>
<script>
</script>
</body>
</html>

Здесь у нас тег div имеет атрибут id со значением div_id. Мы это
значение придумываем сами, главное, чтобы оно было уникальным в пределах HTML-страницы.
Теперь можно получить этот элемент div по этому id, где бы он ни
находился в DOM-дереве. Для
этого используется метод getElementById объекта document:

let divElem = document.getElementById('div_id');
console.log( divElem );

Мы в методе getElementById в качестве
аргумента указываем строку со значением атрибута id и на выходе
получаем ссылку на этот элемент.

Или же можем
получить доступ к этому элементу напрямую через глобальную переменную div_id, которая
автоматически создается браузером при формировании DOM-дерева:

Но этот второй
способ имеет одну уязвимость: если мы создадим в скрипте переменную с таким же
именем, то прежнее значение div_id будет затерто:

let div_id = "не тег div";

так что этот
подход следует применять с большой осторожностью, лучше использовать метод getElementById.

В стандарте ES6+ появился
новый метод поиска элементов querySelectorAll, который возвращает список
элементов, удовлетворяющих CSS-селектору, который мы в нем указываем.

Например,
добавим в HTML-документ вот
такой маркированный список:

<ul>
<li>Солнце
<li>Меркурий
<li>Венера
<li>Земля
<li>Марс
</ul>

и вот такой
нумерованный список:

<p>Звезды:
<ol>
<li>Сириус
<li>Альдебаран
<li>Капелла
<li>Ригель
</ol>

И теперь хотим
выбрать все теги <li>, но только у маркированного списка.
Для этого запишем метод querySelectorAll с таким CSS-селектором:

let list = document.querySelectorAll("ul > li");
for(let val of list)
   console.log(val);

Как видите, у
нас были выбраны элементы только маркированного списка. Мало того, в методе querySelectorAll
можно использовать псевдоклассы для указания более сложных CSS-селекторов,
например, так:

let list = document.querySelectorAll("ul > li:first-child");

тогда мы увидим
только первый элемент списка. И так далее. С помощью querySelectorAll можно довольно
просто выбрать нужные элементы и далее производить с ними необходимые действия.

Если же нам
нужно по определенному CSS-селектору найти только первый
подходящий элемент, то для этого применяется другой метод querySelector:

let element = document.querySelector("ol > li");
console.log(element);

Здесь мы из
нумерованного списка выбрали первый тег <li>. Конечно,
здесь можно было бы использовать и предыдущий метод querySelectorAll, а затем,
взять из списка только первый:

let element = document.querySelectorAll("ol > li")[0];

но это будет
работать медленнее, так как все равно сначала будут находиться все подходящие
элементы, а затем, браться первый. Поэтому, если нужно найти только первый
подходящий, то следует использовать querySelector.

Следующий метод matches
позволяет определить: подходит ли данный элемент под указанный CSS-селектор или
нет. Если подходит, то возвращает true, иначе – false. Например,
создадим такое оглавление:

<h1>О звездах</h1>
<div class="content-table">
       <ul class="stars-list">
              <li class="star">О сириусе</li>
              <li class="star">Об альдебаране</li>
              <li class="contact">Обратная связь</li>
       </ul>
</div>

И мы, перебирая
список пунктов меню, хотим выбрать только те элементы, у которых class равен star. Это можно сделать так:

let list = document.querySelectorAll("ul.stars-list > li");
 
for(let item of list) {
     if(item.matches("li.star")) console.log(item);
}

Обратите
внимание, что метод matches относится к объекту DOM, а не к document. Что, в
общем-то логично, так как нам нужно проверить конкретный элемент на
соответствие CSS-селектора. В
результате, в консоле мы увидим первые два элемента:

<li class="star">О сириусе</li>
<li class="star">Об альдебаране</li>

Следующий метод
elem.closest(css) ищет ближайшего предка, который соответствует CSS-селектору.
Сам элемент также включается в поиск. Метод возвращает либо предка, либо null,
если такой элемент не найден. Например:

let li = document.querySelector("li.star");
console.log(li.closest('.stars-list'));
console.log(li.closest('.content-table'));
console.log(li.closest('h1')); // null

Сначала мы
выбираем первый элемент li пункта меню.
Затем, с помощью метода closest ищем ближайшего родителя с
классом stars-list. Находится
список ul. Далее, ищем
родителя с классом content-table. Находим блок div. Наконец,
пытаемся найти родителя с тегом h1. Но его нет, так как h1 в документе не
является родителем для объекта li. Получаем значение null.

В старых версиях
языка JavaScript (стандарта ES5-) существуют
следующие методы для поиска элементов:

  • elem.getElementsByTagName(tag)
    ищет элементы с указанным тегом и возвращает их коллекцию. Указав “*”
    вместо тега, можно получить всех потомков.

  • elem.getElementsByClassName(className)
    возвращает элементы, которые имеют указанный CSS-класс.

  • document.getElementsByName(name)
    возвращает элементы с заданным атрибутом name. (Используется очень редко).

Применение этих
методов очевидно и я привел их здесь лишь для полноты картины, так как вы
можете их встретить в старых скриптах. Вместо всех этих методов теперь
используются методы querySelector и querySelectorAll. Они дают
гораздо больше возможностей, чем старые методы.

Однако, между методами
getElementsBy* и querySelector, querySelectorAll есть одно важное
отличие: методы getElementsBy* возвращают, так называемую, «живую»
коллекцию, то есть, они всегда отражают текущее состояние документа и автоматически
обновляются при его изменении. Например, при загрузке и отображении такого HTML-документа:

<!DOCTYPE html>
<html>
<head>
    <title>Уроки по JavaScript</title>
</head>
<body>
<h1>О звездах</h1>
<h2>Об альдебаране</h2>
<script>
    let list = document.getElementsByTagName("h2");
</script>
<h2>О ригеле</h2>
<script>
    for(let item of list) console.log(item);
</script>
</body>
</html>

Мы в консоле
увидим список из двух тегов h2, хотя когда этот список формировался в
HTML-документе был
всего один тег h2. Второй добавился позже, автоматически. Но, если
мы будем получать список этих элементов с помощью метода querySelectorAll:

   let list = document.querySelectorAll("h2");

то увидим только
один тег h2. Так как здесь
делается как бы снимок коллекции на текущий момент состояния HTML-документа и
после этого никак не меняется. Вот этот момент при работе с этими методами
следует иметь в виду.

Ну и в
заключение этого занятия отметим еще один полезный метод

который
возвращает значение true, если elemB является дочерним по отношению к elemA.
И false в противном
случае. Например, вот в этом документе:

<div class="content-table">
    <ul class="stars-list">
       <li class="star">О сириусе</li>
       <li class="star">Об альдебаране</li>
       <li class="contact">Обратная связь</li>
    </ul>
</div>

Можно проверить:
имеется ли список внутри тега div:

let div = document.querySelector("div.content-table");
let ul = document.querySelector("ul.stars-list");
 
if(div.contains(ul)) 
     console.log("ul внутри div");

Вот такие
основные методы поиска элементов в DOM-дереве есть в JavaScript.

Видео по теме

image

Основные источники

  • DOM Living Standart
  • HTML Living Standart
  • Document Object Model (DOM) Level 3 Core Specification
  • DOM Parsing and Serialization

Введение

JavaScript предоставляет множество методов для работы с Document Object Model или сокращенно DOM (объектной моделью документа): одни из них являются более полезными, чем другие; одни используются часто, другие почти никогда; одни являются относительно новыми, другие признаны устаревшими.

Я постараюсь дать вам исчерпывающее представление об этих методах, а также покажу парочку полезных приемов, которые сделают вашу жизнь веб-разработчика немного легче.

Размышляя над подачей материала, я пришел к выводу, что оптимальным будет следование спецификациям с промежуточными и заключительными выводами, сопряженными с небольшими лирическими отступлениями.

Сильно погружаться в теорию мы не будем. Вместо этого, мы сосредоточимся на практической составляющей.

Для того, чтобы получить максимальную пользу от данной шпаргалки, пишите код вместе со мной и внимательно следите за тем, что происходит в консоли инструментов разработчика и на странице.

Вот как будет выглядеть наша начальная разметка:

<ul id="list" class="list">
  <li id="item1" class="item">1</li>
  <li id="item2" class="item">2</li>
  <li id="item3" class="item">3</li>
</ul>

У нас есть список (ul) с тремя элементами (li). Список и каждый элемент имеют идентификатор (id) и CSS-класс (class). id и class — это атрибуты элемента. Существует множество других атрибутов: одни из них являются глобальными, т.е. могут добавляться к любому элементу, другие — локальными, т.е. могут добавляться только к определенным элементам.

Мы часто будем выводить данные в консоль, поэтому создадим такую “утилиту”:

const log = console.log

Миксин NonElementParentNode

Данный миксин предназначен для обработки (браузером) родительских узлов, которые не являются элементами.

В чем разница между узлами (nodes) и элементами (elements)? Если кратко, то “узлы” — это более общее понятие, чем “элементы”. Узел может быть представлен элементом, текстом, комментарием и т.д. Элемент — это узел, представленный разметкой (HTML-тегами (открывающим и закрывающим) или, соответственно, одним тегом).

У рассматриваемого миксина есть метод, наследуемый от объекта Document, с которого удобно начать разговор.

Небольшая оговорка: разумеется, мы могли бы создать список и элементы программным способом.

Для создания элементов используется метод createElement(tag) объекта Document:

const listEl = document.createElement('ul')

Такой способ создания элементов называется императивным. Он является не очень удобным и слишком многословным: создаем родительский элемент, добавляет к нему атрибуты по одному, внедряем его в DOM, создаем первый дочерний элемент и т.д. Следует отметить, что существует и другой, более изысканный способ создания элементов — шаблонные или строковые литералы (template literals), но о них мы поговорим позже.

Одним из основных способов получения элемента (точнее, ссылки на элемент) является метод getElementById(id) объекта Document:

// получаем ссылку на наш список
const listEl = document.getElementById('list')
log(listEl)
// ul#list.list - такая запись означает "элемент `ul` с `id === list`" и таким же `class`

Почему идентификаторы должны быть уникальными в пределах приложения (страницы)? Потому что элемент с id становится значением одноименного свойства глобального объекта window:

log(listEl === window.list) // true

Как мы знаем, при обращении к свойствам и методам window, слово window можно опускать, например, вместо window.localStorage можно писать просто localStorage. Следовательно, для доступа к элементу с id достаточно обратиться к соответствующему свойству window:

log(list) // ul#list.list

Обратите внимание, что это не работает в React и других фреймворках, абстрагирующих работу с DOM, например, с помощью Virtual DOM. Более того, там иногда невозможно обратиться к нативным свойствам и методам window без window.

Миксин ParentNode

Данный миксин предназначен для обработки родительских элементов (предков), т.е. элементов, содержащих одного и более потомка (дочерних элементов).

  • children — потомки элемента

const { children } = list // list.children
log(children)
/*
HTMLCollection(3)
  0: li#item1.item
  1: li#item2.item
  2: li#item3.item
  length: 3
*/

Такая структура называется коллекцией HTML и представляет собой массивоподобный объект (псевдомассив). Существует еще одна похожая структура — список узлов (NodeList).

Массивоподобные объекты имеют свойство length с количеством потомков, метод forEach() (NodeList), позволяющий перебирать узлы (делать по ним итерацию). Такие объекты позволяют получать элементы по индексу, по названию (HTMLCollection) и т.д. Однако, у них отсутствуют методы настоящих массивов, такие как map(), filter(), reduce() и др., что делает работу с ними не очень удобной. Поэтому массивоподобные объекты рекомендуется преобразовывать в массивы с помощью метода Array.from() или spread-оператора:

const children = Array.from(list.children)
// или
const children = [...list.children]
log(children) // [li#item1.item, li#item2.item, li#item3.item] - обычный массив

  • firstElementChild — первый потомок — элемент
  • lastElementChild — последний потомок — элемент

log(list.firstElementChild) // li#item1.item
log(list.lastElementChild) // li#item2.item

Для дальнейших манипуляций нам потребуется периодически создавать новые элементы, поэтому создадим еще одну утилиту:

const createEl = (id, text, tag = 'li', _class = 'item') => {
  const el = document.createElement(tag)
  el.id = id
  el.className = _class
  el.textContent = text
  return el
}

Наша утилита принимает 4 аргумента: идентификатор, текст, название тега и CSS-класс. 2 аргумента (тег и класс) имеют значения по умолчанию. Функция возвращает готовый к работе элемент. Впоследствии, мы реализуем более универсальный вариант данной утилиты.

  • prepend(newNode) — добавляет элемент в начало списка
  • append(newNode) — добавляет элемент в конец списка

// создаем новый элемент
const newItem = createEl('item0', 0)
// и добавляем его в начало списка
list.prepend(newItem)

// создаем еще один элемент
const newItem2 = createEl('item4', 4)
// и добавляем его в конец списка
list.append(newItem2)

log(children)
/*
HTMLCollection(5)
  0: li#item0.item
  1: li#item1.item
  2: li#item2.item
  3: li#item3.item
  4: li#item4.item
*/

Одной из интересных особенностей HTMLCollection является то, что она является “живой”, т.е. элементы, возвращаемые по ссылке, и их количество обновляются автоматически. Однако, эту особенность нельзя использовать, например, для автоматического добавления обработчиков событий.

  • replaceChildren(nodes) — заменяет потомков новыми элементами

const newItems = [newItem, newItem2]
// заменяем потомков новыми элементами
list.replaceChildren(...newItems) // list.replaceChildren(newItem, newItem2)

log(children) // 2

Наиболее универсальными способами получения ссылок на элементы являются методы querySelector(selector) и querySelectorAll(selector). Причем, в отличие от getElementById(), они могут вызываться на любом родительском элементе, а не только на document. В качестве аргумента названным методам передается любой валидный CSS-селектор (id, class, tag и т.д.):

// получаем элемент `li` с `id === item0`
const itemWithId0 = list.querySelector('#item0')
log(itemWithId0) // li#item0.item

// получаем все элементы `li` с `class === item`
const allItems = list.querySelectorAll('.item')
log(allItems) // массивоподобный объект
/*
NodeList(2)
  0: li#item0.item
  1: li#item4.item
  length: 2
*/

Создадим универсальную утилиту для получения элементов:

const getEl = (selector, parent = document, single = true) => single ? parent.querySelector(selector) : [...parent.querySelectorAll(selector)]

Наша утилита принимает 3 аргумента: CSS-селектор, родительский элемент и индикатор количества элементов (один или все). 2 аргумента (предок и индикатор) имеют значения по умолчанию. Функция возвращает либо один, либо все элементы (в виде обычного массива), совпадающие с селектором, в зависимости от значения индикатора:

const itemWithId0 = getEl('#item0', list)
log(itemWithId0) // li#item0.item

const allItems = getEl('.item', list, false)
log(allItems) // [li#item0.item, li#item4.item]

Миксин NonDocumentTypeChildNode

Данный миксин предназначен для обработки дочерних узлов, которые не являются документом, т.е. всех узлов, кроме document.

  • previousElementSibling — предыдущий элемент
  • nextElementSibling — следующий элемент

log(itemWithId0.previousElementSibling) // null
log(itemWithId0.nextElementSibling) // #item4

Миксин ChildNode

Данный миксин предназначен для обработки дочерних элементов, т.е. элементов, являющихся потомками других элементов.

  • before(newNode) — вставляет новый элемент перед текущим
  • after(newNode) — вставляет новый элемент после текущего

// получаем `li` с `id === item4`
const itemWithId4 = getEl('#item4', list)
// создаем новый элемент
const newItem3 = createEl('item3', 3)
// и вставляем его перед `itemWithId4`
itemWithId4.before(newItem3)

// создаем еще один элемент
const newItem4 = createEl('item2', 2)
// и вставляем его после `itemWithId0`
itemWithId0.after(newItem4)

  • replaceWith(newNode) — заменяет текущий элемент новым

// создаем новый элемент
const newItem5 = createEl('item1', 1)
// и заменяем им `itemWithId0`
itemWithId0.replaceWith(newItem5)

  • remove() — удаляет текущий элемент

itemWithId4.remove()

Интерфейс Node

Данный интерфейс предназначен для обработки узлов.

  • nodeType — тип узла

log(list.nodeType) // 1

// другие варианты
/*
 1 -> ELEMENT_NODE (элемент)
 3 -> TEXT_NODE (текст)
 8 -> COMMENT_NODE (комментарий)
 9 -> DOCUMENT_NODE (document)
 10 -> DOCUMENT_TYPE_NODE (doctype)
 11 -> DOCUMENT_FRAGMENT_NODE (фрагмент) и т.д.
*/

  • nodeName — название узла

log(list.nodeName) // UL

// другие варианты
/*
  - квалифицированное название HTML-элемента прописными (заглавными) буквами
  - квалифицированное название атрибута
  - #text
  - #comment
  - #document
  - название doctype
  - #document-fragment
*/

  • baseURI — основной путь

log(list.baseURI) // .../dom/index.html

  • parentNode — родительский узел
  • parentElement — родительский элемент

const itemWithId1 = getEl('#item1', list)

log(itemWithId1.parentNode) // #list
log(itemWithId1.parentElement) // #list

  • hasChildNodes() — возвращает true, если элемент имеет хотя бы одного потомка
  • childNodes — дочерние узлы

log(list.hasChildNodes()) // true
log(list.childNodes)
/*
NodeList(3)
  0: li#item1.item
  1: li#item2.item
  2: li#item3.item
*/

  • firstChild — первый потомок — узел
  • lastChild — последний потомок — узел

log(list.firstChild) // #item1
log(list.lastChild) // #item3

  • nextSibling — следующий узел
  • previousSibling — предыдущий узел

log(itemWithId1.nextSibling) // #item2
log(itemWithId1.previousSibling) // null

  • textContent — геттер/сеттер для извлечения/записи текста

// получаем текст
log(itemWithId1.textContent) // 1
// меняем текст
itemWithId1.textContent = 'item1'
log(itemWithId1.textContent) // item1

// получаем текстовое содержимое всех потомков
log(list.textContent) // item123

Для извлечения/записи текста существует еще один (устаревший) геттер/сеттер — innerText.

  • cloneNode(deep) — копирует узел. Принимает логическое значение, определяющее характер копирования: поверхностное — копируется только сам узел, глубокое — копируется сам узел и все его потомки

// создаем новый список посредством копирования существующего
const newList = list.cloneNode(false)
// удаляем у него `id` во избежание коллизий
newList.removeAttribute('id')
// меняем его текстовое содержимое
newList.textContent = 'new list'
// и вставляем его после существующего списка
list.after(newList)

// создаем еще один список
const newList2 = newList.cloneNode(true)
newList.after(newList2)

  • isEqualNode(node) — сравнивает узлы
  • isSameNode(node) — определяет идентичность узлов

log(newList.isEqualNode(newList2)) // true
log(newList.isSameNode(newList2)) // false

  • contains(node) — возвращает true, если элемент содержит указанный узел

log(list.contains(itemWithId1)) // true

  • insertBefore(newNode, existingNode) — добавляет новый узел (newNode) перед существующим (existingNode)

// создаем новый элемент
const itemWithIdA = createEl('#item_a', 'a')
// и вставляем его перед `itemWithId1`
list.insertBefore(itemWithIdA, itemWithId1)

  • appendChild(node) — добавляет узел в конец списка

// создаем новый элемент
const itemWithIdC = createEl('#item_c', 'c')
// и добавляем его в конец списка
list.appendChild(itemWithIdC)

  • replaceChild(newNode, existingNode) — заменяет существующий узел (existingNode) новым (newNode):

// создаем новый элемент
const itemWithIdB = createEl('item_b', 'b')
// и заменяем им `itemWithId1`
list.replaceChild(itemWithIdB, itemWithId1)

  • removeChild(node) — удаляет указанный дочерний узел

// получаем `li` с `id === item2`
const itemWithId2 = getEl('#item2', list)
// и удаляем его
list.removeChild(itemWithId2)

Интерфейс Document

Данный интерфейс предназначен для обработки объекта Document.

  • URL и documentURI — адрес документа

log(document.URL) // .../dom/index.html
log(document.documentURI) // ^

  • documentElement:

log(document.documentElement) // html

  • getElementsByTagName(tag) — возвращает все элементы с указанным тегом

const itemsByTagName = document.getElementsByTagName('li')
log(itemsByTagName)
/*
HTMLCollection(4)
  0: li##item_a.item
  1: li#item_b.item
  2: li#item3.item
  3: li##item_c.item
*/

  • getElementsByClassName(className) — возвращает все элементы с указанным CSS-классом

const itemsByClassName = list.getElementsByClassName('item')
log(itemsByClassName) // ^

  • createDocumentFragment() — возвращает фрагмент документа:

// создаем фрагмент
const fragment = document.createDocumentFragment()
// создаем новый элемент
const itemWithIdD = createEl('item_d', 'd')
// добавляем элемент во фрагмент
fragment.append(itemWithIdD)
// добавляем фрагмент в список
list.append(fragment)

Фрагменты позволяют избежать создания лишних элементов. Они часто используются при работе с разметкой, скрытой от пользователя с помощью тега template (метод cloneNode() возвращает DocumentFragment).

  • createTextNode(data) — создает текст

  • createComment(data) — создает комментарий

  • importNode(existingNode, deep) — создает новый узел на основе существующего

// создаем новый список на основе существующего
const newList3 = document.importNode(list, true)
// вставляем его перед существующим списком
list.before(newList3)
// и удаляем во избежание коллизий
newList3.remove()

  • createAttribute(attr) — создает указанный атрибут

Интерфейсы NodeIterator и TreeWalker

Интерфейсы NodeIterator и TreeWalker предназначены для обхода (traverse) деревьев узлов. Я не сталкивался с примерами их практического использования, поэтому ограничусь парочкой примеров:

// createNodeIterator(root, referenceNode, pointerBeforeReferenceNode, whatToShow, filter)
const iterator = document.createNodeIterator(list)
log(iterator)
log(iterator.nextNode()) // #list
log(iterator.nextNode()) // #item_a
log(iterator.previousNode()) // #item_a
log(iterator.previousNode()) // #list
log(iterator.previousNode()) // null

// createTreeWalker(root, whatToShow, filter)
// применяем фильтры - https://dom.spec.whatwg.org/#interface-nodefilter
const walker = document.createTreeWalker(list, '0x1', { acceptNode: () => 1 })
log(walker)
log(walker.parentNode()) // null
log(walker.firstChild()) // #item_a
log(walker.lastChild()) // null
log(walker.previousSibling()) // null
log(walker.nextSibling()) // #item_b
log(walker.nextNode()) // #item3
log(walker.previousNode()) // #item_b

Интерфейс Element

Данный интерфейс предназначен для обработки элементов.

  • localName и tagName — название тега

log(list.localName) // ul
log(list.tagName) // UL

  • id — геттер/сеттер для идентификатора
  • className — геттер/сеттер для CSS-класса

log(list.id) // list
list.id = 'LIST'
log(LIST.className) // list

  • classList — все CSS-классы элемента (объект DOMTokenList)

const button = createEl('button', 'Click me', 'my_button', 'btn btn-primary')
log(button.classList)
/*
DOMTokenList(2)
  0: "btn"
  1: "btn-primary"
  length: 2
  value: "btn btn-primary"
*/

Работа с classList

  • classList.add(newClass) — добавляет новый класс к существующим
  • classList.remove(existingClass) — удаляет указанный класс
  • classList.toggle(className, force?) — удаляет существующий класс или добавляет новый. Если опциональный аргумент force имеет значение true, данный метод только добавляет новый класс при отсутствии, но не удаляет существующий класс (в этом случае toggle() === add()). Если force имеет значение false, данный метод только удаляет существующий класс при наличии, но не добавляет отсутствующий класс (в этом случае toggle() === remove())
  • classList.replace(existingClass, newClass) — заменяет существующий класс (existingClass) на новый (newClass)
  • classList.contains(className) — возвращает true, если указанный класс обнаружен в списке классов элемента (данный метод идентичен className.includes(className))

// добавляем к кнопке новый класс
button.classList.add('btn-lg')
// удаляем существующий класс
button.classList.remove('btn-primary')
// у кнопки есть класс `btn-lg`, поэтому он удаляется
button.classList.toggle('btn-lg')
// заменяем существующий класс на новый
button.classList.replace('btn', 'btn-success')

log(button.className) // btn-success
log(button.classList.contains('btn')) // false
log(button.className.includes('btn-success')) // true

Работа с атрибутами

  • hasAttributes() — возвращает true, если у элемента имеются какие-либо атрибуты
  • getAttributesNames() — возвращает названия атрибутов элемента
  • getAttribute(attrName) — возвращает значение указанного атрибута
  • setAttribute(name, value) — добавляет указанные атрибут и его значение к элементу
  • removeAttribute(attrName) — удаляет указанный атрибут
  • hasAttribute(attrName) — возвращает true при наличии у элемента указанного атрибута
  • toggleAttribute(name, force) — добавляет новый атрибут при отсутствии или удаляет существующий атрибут. Аргумент force аналогичен одноименному атрибуту classList.toggle()

log(button.hasAttributes()) // true
log(button.getAttributeNames()) // ['id', 'class']
log(button.getAttribute('id')) // button
button.setAttribute('type', 'button')
button.removeAttribute('class')
log(button.hasAttribute('class')) // false

В использовании перечисленных методов для работы с атрибутами нет особой необходимости, поскольку многие атрибуты являются геттерами/сеттерами, т.е. позволяют извлекать/записывать значения напрямую. Единственным исключением является метод removeAttribute(), поскольку существуют атрибуты без значений: например, если кнопка имеет атрибут disabled, установка значения данного атрибута в false не приведет к снятию блокировки — для этого нужно полностью удалить атрибут disabled с помощью removeAttribute().

Отдельного упоминания заслуживает атрибут data-*, где символ * означает любую строку. Он предназначен для определения пользовательских атрибутов. Например, в нашей начальной разметке для уникальной идентификации элементов используется атрибут id. Однако, это приводит к загрязнению глобального пространства имен, что чревато коллизиями между нашими переменными и, например, переменными используемой нами библиотеки — когда какой-либо объект библиотеки пытается записаться в свойство window, которое уже занято нашим id.

Вместо этого, мы могли бы использовать атрибут data-id и получать ссылки на элементы с помощью getEl('[data-id="id"]').

Название data-атрибута после символа - становится одноименным свойством объекта dataset. Например, значение атрибута data-id можно получить через свойство dataset.id.

  • closest(selectors) — возвращает первый родительский элемент, совпавший с селекторами

LIST.append(button)
log(button.closest('#LIST', 'document.body')) // #LIST

  • matches(selectors) — возвращает true, если элемент совпадает хотя бы с одним селектором

log(button.matches('.btn', '[type="button"]'))
// у кнопки нет класса `btn`, но есть атрибут `type` со значением `button`,
// поэтому возвращается `true`

  • insertAdjacentElement(where, newElement) — универсальный метод для вставки новых элементов перед/в начало/в конец/после текущего элемента. Аргумент where определяет место вставки. Возможные значения:

    • beforebegin — перед открывающим тегом
    • afterbegin — после открывающего тега
    • beforeend — перед закрывающим тегом
    • afterend — после закрывающего тега

  • insertAdjacentText(where, data) — универсальный метод для вставки текста

  • Text — конструктор для создания текста

  • Comment — конструктор для создания комментария

const text = new Text('JavaScript')
log(text) // "JavaScript"

const part = text.splitText(4)
log(part) // "Script"
log(part.wholeText()) // Script

const comment = new Comment('TODO')
log(comment) // <!--TODO-->

Объект Document

  • location — объект с информацией о текущей локации документа

log(document.location)

Свойства объекта location:

  • hash — хэш-часть URL (символ # и все, что следует за ним), например, #top
  • host — название хоста и порт, например, localhost:3000
  • hostname — название хоста, например, localhost
  • href — полный путь
  • originprotocol + host
  • pathname — путь без протокола
  • port — порт, например, 3000
  • protocol — протокол, например, https
  • search — строка запроса (символ ? и все, что следует за ним), например, ?name=John&age=30

Методы location:

  • reload() — перезагружает текущую локацию

  • replace() — заменяет текущую локацию на новую

  • title — заголовок документа

log(document.title) // DOM

  • head — метаданные документа

  • body — тело документа

  • images — псевдомассив (HTMLCollection), содержащий все изображения, имеющиеся в документе

const image = document.createElement('img')
image.className = 'my_image'
image.src = 'https://miro.medium.com/max/875/1*ZIH_wjqDfZn6NRKsDi9mvA.png'
image.alt = "V8's compiler pipeline"
image.width = 480
document.body.append(image)
log(document.images[0]) // .my_image

  • links — псевдомассив, содержащий все ссылки, имеющиеся в документе

const link = document.createElement('a')
link.className = 'my_link'
link.href = 'https://github.com/azat-io/you-dont-know-js-ru'
link.target = '_blank'
link.rel = 'noopener noreferrer'
link.textContent = 'Вы не знаете JS'
document.body.append(link)
log(document.links[0]) // .my_link

  • forms — псевдомассив, содержащий все формы, имеющиеся в документе

const form = document.createElement('form')
form.className = 'my_form'
document.body.append(form)
log(document.forms[0]) // .my_form

Следующие методы и свойство считаются устаревшими:

  • open() — открывает документ для записи. При этом документ полностью очищается
  • close() — закрывает документ для записи
  • write() — записывает данные (текст, разметку) в документ
  • writeln() — записывает данные в документ с переносом на новую строку
  • designMode — управление режимом редактирования документа. Возможные значения: on и off. Наберите document.designMode = 'on' в консоли DevTools и нажмите Enter. Вуаля, страница стала редактируемой: можно удалять/добавлять текст, перетаскивать изображения и т.д.
  • execCommand() — выполняет переданные команды. Со списоком доступных команд можно ознакомиться здесь. Раньше этот метод активно использовался для записи/извлечения данных из буфера обмена (команды copy и paste). Сейчас для этого используются методы navigator.clipboard.writeText(), navigator.clipboard.readText() и др.

Миксин InnerHTML

Геттер/сеттер innerHTML позволяет извлекать/записывать разметку в элемент. Для подготовки разметки удобно пользоваться шаблонными литералами:

const itemsTemplate = `
  <li data-id="item1" class="item">1</li>
  <li data-id="item2" class="item">2</li>
  <li data-id="item3" class="item">3</li>
`
LIST.innerHTML = itemsTemplate
log(LIST.innerHTML)
/*
<li data-id="item1" class="item">1</li>
<li data-id="item2" class="item">2</li>
<li data-id="item3" class="item">3</li>
*/

Расширения интерфейса Element

  • outerHTML — геттер/сеттер для извлечения/записи внешней разметки элемента: то, что возвращает innerHTML + разметка самого элемента

log(LIST.outerHTML)
/*
<ul id="LIST" class="list">
  <li data-id="item1" class="item">1</li>
  <li data-id="item2" class="item">2</li>
  <li data-id="item3" class="item">3</li>
</ul>
*/

  • insertAdjacentHTML(where, string) — универсальный метод для вставки разметки в виде строки. Аргумент where аналогичен одноименному аргументу метода insertAdjacentElement()

Метод insertAdjacentHTML() в сочетании с шаблонными литералами и их продвинутой версией — тегированными шаблонными литералами (tagged template literals) предоставляет много интересных возможностей по манипулированию разметкой документа. По сути, данный метод представляет собой движок шаблонов (template engine) на стороне клиента, похожий на Pug, Handlebars и др. серверные движки. С его помощью (при участии History API) можно, например, реализовать полноценное одностраничное приложение (Single Page Application или сокращенно SPA). Разумеется, для этого придется написать чуть больше кода, чем при использовании какого-либо фронтенд-фреймворка.

Вот несколько полезных ссылок, с которых можно начать изучение этих замечательных инструментов:

  • Element.insertAdjacentHTML() — MDN
  • Изменение документа — JSR
  • Шаблонные строки — MDN
  • Template Literals — ECMAScript 2022

Иногда требуется создать элемент на основе шаблонной строки. Как это можно сделать? Вот соответствующая утилита:

const createElFromStr = (str) => {
  // создаем временный элемент
  const el = document.createElement('div')
  // записываем в него переданную строку - разметку
  el.innerHTML = str
  // извлекаем наш элемент
  // если мы используем здесь метод `firstChild()`, может вернуться `#text`
  // одна из проблем шаблонных строк заключается в большом количестве лишних пробелов
  const child = el.fisrtElementChild
  // удаляем временный элемент
  el.remove()
  // и возвращаем наш элемент
  return child
}

// шаблон списка
const listTemplate = `
<ul id="list">
  <li data-id="item1" class="item">1</li>
  <li data-id="item2" class="item">2</li>
  <li data-id="item3" class="item">3</li>
</ul>
`

// создаем список на основе шаблона
const listEl = createElFromStr(listTemplate)
// и вставляем его в тело документа
document.body.append(listEl)

Существует более экзотический способ создания элемента на основе шаблонной строки. Он предполагает использование конструктора DOMParser():

const createElFromStr = (str) => {
  // создаем новый парсер
  const parser = new DOMParser()
  // парсер возвращает новый документ
  const {
    body: { children }
  } = parser.parseFromString(str, 'text/html')
  // нас интересует первый дочерний элемент тела нового документа
  return children[0]
}

const listTemplate = `
<ul id="list">
  <li data-id="item1" class="item">1</li>
  <li data-id="item2" class="item">2</li>
  <li data-id="item3" class="item">3</li>
</ul>
`

const listEl = createElFromStr(listTemplate)
document.body.append(listEl)

Еще более экзотический, но при этом самый короткий способ предполагает использование расширения для объекта Range — метода createContextualFragment():

const createElFromStr = (str) => {
  // создаем новый диапазон
  const range = new Range()
  // создаем фрагмент
  const fragment = range.createContextualFragment(str)
  // и возвращаем его
  return fragment
}

// или в одну строку
const createFragment = (str) => new Range().createContextualFragment(str)

const listTemplate = `
<ul id="list">
  <li data-id="item1" class="item">1</li>
  <li data-id="item2" class="item">2</li>
  <li data-id="item3" class="item">3</li>
</ul>
`

document.body.append(createFragment(listTemplate))

В завершение, как и обещал, универсальная утилита для создания элементов:

// функция принимает название тега и объект с настройками
const createEl = (tag, opts) => {
  const el = document.createElement(tag)
  // перебираем ключи объекта и записывает соответствующие свойства в элемент
  for (const key in opts) {
    el[key] = opts[key]
  }
  // возвращаем готовый элемент
  return el
}

const button = createEl('button', {
  // настройками могут быть атрибуты
  id: 'my_button',
  className: 'btn btn-primary',
  textContent: 'Click me',

  title: 'My button',
  autofocus: true,

  // стили
  style: 'color: red; cursor: pointer;',

  // обработчики и т.д.
  onmouseenter: function () {
    this.style.color = 'green'
  },
  onmouseout: function () {
    this.style.color = 'blue'
  },

  onclick: () => alert('Привет!')
})

document.body.append(button)

Заключение

Современный JS предоставляет богатый арсенал методов для работы с DOM. Данных методов вполне достаточно для решения всего спектра задач, возникающих при разработке веб-приложений. Хорошее знание этих методов, а также умение их правильно применять гарантируют не только высокое качество (чистоту) кода, но также избавляют от необходимости использовать DOM-библиотеки (такие как jQuery), что в совокупности обусловливает производительность приложения, его поддерживаемость и масштабируемость.

Разумеется, шпаргалка — это всего лишь карманный справочник, памятка для быстрого восстановления забытого материала, предполагающая наличие определенного багажа знаний.


VDS от Маклауд быстрые и безопасные.

Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!

В этой главе рассказывается, как найти и получить доступ к HTML элементам на HTML странице.

Поиск HTML элементов

Часто в JavaScript необходимо проводить определенные манипуляции с HTML элементами.

Чтобы это сделать, сначала нужно найти требуемый элемент. Найти HTML элемент можно несколькими способами:

  • по идентификатору id
  • по имени тега
  • по имени класса
  • по селекторам CSS
  • по наборам объектов HTML

Поиск HTML элемента по идентификатору

Самый простой способ найти HTML элемент в DOM — это использовать его идентификатор id.

В следующем примере мы ищем элемент с id=”intro”:


var myElement = document.getElementById("intro"); 

Если элемент будет найден, то он будет возвращен в виде объекта (в переменную myElement).

Если элемент не будет найден, то в переменная myElement будет содержать значение null.

Поиск HTML элемента по имени тега

В следующем примере мы ищем все элементы <p>:


var x = document.getElementsByTagName("p"); 

В следующем примере сначала происходит поиск элемента с id=”main”, а затем всех элементов <p> внутри “main”:


var x = document.getElementById("main");
var y = x.getElementsByTagName("p"); 

Поиск HTML элемента по имени класса

Если нужно найти все HTML элементы с одним и тем же именем класса, то используют метод getElementsByClassName().

В следующем примере возвращается список всех элементов с атрибутом class=”intro”:


var x = document.getElementsByClassName("intro"); 

Внимание! Поиск элементов по имени класса не работает в Internet Explorer 8 и более ранних версиях.

Поиск HTML элемента по CSS селекторам

Если нужно найти все HTML элементы, подходящие по заданному CSS селектору (id, имена классов, типы, атрибуты, значения атрибутов и т.п.), используется метод querySelectorAll().

В следующем примере возвращается список всех элементов <p> с атрибутом class=”intro”:


var x = document.querySelectorAll("p.intro"); 

Внимание! Метод querySelectorAll() не работает в Internet Explorer 8 и более ранних версиях.

Поиск HTML элемента по наборам HTML объектов

В следующем примере производится поиск элемента формы с атрибутом id=”frm1″ в наборе объектов forms, и отображаются все значения элементов:


var x = document.forms["frm1"];
var text = "";
var i;
for (i = 0; i < x.length; i++) {
    text += x.elements[i].value + "<br>";
}
document.getElementById("demo").innerHTML = text; 

Также доступны следующие HTML объекты (и наборы объектов):

  • document.anchors
  • document.body
  • document.documentElement
  • document.embeds
  • document.forms
  • document.head
  • document.images
  • document.links
  • document.scripts
  • document.title

Добавить комментарий