getElementById и старые методы

поиск≈ 7 минут

Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.

Метод document.getElementById старше jQuery и переживёт ещё не одну моду — как и его соседи getElementsByClassName и getElementsByTagName. Разберём эту троицу и её главную особенность: живые коллекции, которые меняются прямо во время перебора.

Разделы темы:·

Если у элемента есть атрибут id, брать его нужно этим методом. Он возвращает либо сам элемент, либо null, если ничего не нашлось. Исключения не будет — проверка результата целиком на вашей совести, и обращение к свойству несуществующего элемента упадёт уже строкой ниже.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Есть и побочный эффект, о котором полезно знать: для каждого id браузер заводит одноимённое свойство у window. Работает во всех браузерах — и всё равно так писать не стоит: любая ваша переменная с тем же именем перекроет это свойство, а читатель кода будет гадать, откуда взялась переменная banner.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

getElementById живёт только на document: у элемента такого метода нет. Логика простая — id уникален на всю страницу, искать его внутри куска разметки бессмысленно.

Когда элементов много, работают два соседних метода: getElementsByClassName ищет по классу, getElementsByTagName — по имени тега ("*" означает «любой»). Оба доступны и у document, и у любого элемента: вызов на элементе ограничивает поиск его потомками. Если в класс передать несколько имён через пробел, найдутся элементы, у которых есть все перечисленные классы; порядок не важен.

Возвращают они не массив, а HTMLCollection. Индексы и length есть, for..of работает, а вот map, filter и forEach — нет. Нужны методы массива — оберните в Array.from.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Звёздочка собирает всех потомков подряд, а несколько классов в строке — это «И», а не «ИЛИ», причём порядок слов роли не играет.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Вот главное отличие старых методов от querySelectorAll: их коллекции живые. Это не снимок, сделанный в момент вызова, а постоянно действующий запрос к документу. Появился подходящий элемент — он сам возник в коллекции; исчез — сам пропал. Повторно вызывать метод не нужно.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Пока вы только читаете length, живость — удобство. Ловушка начинается, когда цикл сам меняет документ: коллекция пересчитывается после каждого шага, индексы съезжают, и часть элементов остаётся необработанной. Классика жанра — перенос элементов из одного контейнера в другой циклом по возрастающему индексу.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Array.from делает снимок: дальше вы перебираете обычный массив, и что происходит с документом, ему уже безразлично.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Соблазн заменить всё на querySelector понятен: один синтаксис вместо трёх методов. Но пара причин оставить старое в живых есть. Первая — getElementById ищет по идентификатору, а не по CSS-селектору, и ему безразлично, что внутри id: точки, двоеточия, скобки. Селектору не безразлично.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Вторая причина — та самая живость, когда она нужна: держите коллекцию в переменной, и её length всегда актуален без повторного поиска. Про скорость думать не стоит: разница между методами заметна разве что в синтетическом тесте на десятки тысяч вызовов.

МетодГде доступенЧто возвращаетЖивая
getElementByIdтолько documentэлемент или null—
getElementsByClassNamedocument и элементHTMLCollectionда
getElementsByTagNamedocument и элементHTMLCollectionда
getElementsByNameтолько documentNodeListда
querySelectorAlldocument и элементNodeListнет

Практическое правило: есть id — getElementById; нужна живая коллекция — getElementsBy*; во всех остальных случаях querySelector и querySelectorAll из прошлой статьи.

  • getElementById есть только у document, возвращает элемент или null и не требует экранирования спецсимволов в id — в отличие от селектора.
  • getElementsByClassName и getElementsByTagName работают и на элементе, ограничивая поиск его потомками; несколько классов через пробел означают «все сразу».
  • Обе коллекции — HTMLCollection: есть индексы, length и for..of, но нет map, filter и forEach; методы массива — через Array.from.
  • Живая коллекция — это запрос, а не снимок: меняя документ внутри цикла по индексам, вы пропустите часть элементов.
  • По умолчанию берите querySelector; старые методы выигрывают, когда есть id или когда живость коллекции нужна специально.

1/3В блоке лежат два элемента с классом x. Код берёт живую коллекцию и циклом for (let i = 0; i < live.length; i++) переносит live[i] в соседний блок. Сколько элементов окажется в новом блоке?

2/3Что найдёт document.getElementsByClassName("card hot")?

3/3Что вернёт document.getElementById("нет-такого")?

Письма застревают в ящике

В блоке #inbox три письма с классом mail. Код должен перенести их все в #archive, но переносит не все. Предположите, что напечатает console.log, объясните почему и почините код, не отказываясь от getElementsByClassName.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Напечатает 2 и 13. Коллекция живая: как только письмо переехало в архив, оно исчезло из mails, а остальные сдвинулись на позицию влево. На шаге i = 0 уходит первое письмо, на шаге i = 1 — уже третье (второе к этому моменту стоит под индексом 0), а на i = 2 длина равна 1, и цикл заканчивается.

Лечится снимком коллекции:

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Второй вариант — наоборот, опереться на живость и всегда брать нулевой элемент: while (mails.length) archive.append(mails[0]);. Работает и перебор индексов с конца, но снимок читается яснее всего.

Ссылки только в подвале

На странице две группы ссылок. Посчитайте ссылки только внутри #footer, не задев шапку. Подсказка: искать необязательно от document.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

getElementsByTagName есть не только у document, но и у любого элемента — и тогда он ищет только среди его потомков.

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Тот же результат даст document.querySelectorAll("#footer a").length. Разница в том, что первая коллекция живая: если в подвал позже добавят ссылку, её length обновится сам, а у второй — нет.