JavaRush /Курсы /Модуль 1: Web Core /Перебор найденных элементов, коллекции vs массивы

Перебор найденных элементов, коллекции vs массивы

Модуль 1: Web Core
22 уровень , 3 лекция
Открыта

1. Что возвращают методы поиска элементов?

Когда мы используем методы поиска элементов на странице, результат зависит от того, какой именно метод мы выбрали:

  • getElementById — всегда возвращает один элемент (или null, если не найден).
  • getElementsByClassName и getElementsByTagName — возвращают коллекцию (HTMLCollection или NodeList), то есть некий "псевдомассив" элементов.
  • querySelector — возвращает первый найденный элемент (или null).
  • querySelectorAll — возвращает коллекцию (NodeList) всех подходящих элементов.

Вот пример:

<ul id="fruits">
  <li class="fruit">Яблоко</li>
  <li class="fruit">Груша</li>
  <li class="fruit">Банан</li>
</ul>
// Получим коллекцию всех элементов с классом 'fruit'
const fruits = document.getElementsByClassName('fruit');

// Получим NodeList всех элементов li внутри #fruits
const fruitItems = document.querySelectorAll('#fruits li');

Вопрос: А можно ли с этими коллекциями работать как с обычными массивами? Вот тут-то и начинаются интересности!

2. Коллекции и массивы: в чём разница?

В JavaScript есть два типа коллекций, которые часто возвращают методы DOM:

  • HTMLCollection — возвращается, например, из getElementsByClassName и getElementsByTagName.
  • NodeList — возвращается из querySelectorAll (и иногда из других методов).

Они очень похожи: оба содержат набор элементов, оба поддерживают доступ по индексу (fruits[0]), у обоих есть свойство length. Но это не совсем массивы! А вот и таблица для сравнения:

Свойство/Метод Массив (Array) HTMLCollection NodeList
Индексация
length
forEach ✅ (NodeList)
map, filter, reduce
push, pop
Живой (live) ❌ (обычно)
Итерация for...of

Что значит "живой" (live)?
HTMLCollection "живая" — если вы добавите или удалите элемент с подходящим классом, коллекция обновится сама. NodeList обычно "неживой" (static): если вы нашли элементы, а потом добавили новый — он не появится в этой коллекции.

3. Перебор коллекций: разные подходы

Старый, но надёжный способ: цикл for

Поскольку и HTMLCollection, и NodeList поддерживают индексацию, мы можем использовать обычный цикл for:

const fruits = document.getElementsByClassName('fruit');

for (let i = 0; i < fruits.length; i++) {
  fruits[i].style.color = 'green'; // Покрасим все фрукты в зелёный
}

Современный подход: for...of

Почти все коллекции DOM поддерживают перебор через for...of (начиная с ES6):

const fruitItems = document.querySelectorAll('#fruits li');

for (const item of fruitItems) {
  item.style.fontWeight = 'bold'; // Сделаем все фрукты жирными
}

forEach: да или нет?

NodeList, возвращаемый из querySelectorAll, поддерживает метод forEach:

const fruitItems = document.querySelectorAll('#fruits li');

fruitItems.forEach(function(item) {
  item.textContent = item.textContent + ' 🍏';
});

Но! HTMLCollection не поддерживает forEach напрямую. Если вы попробуете:

const fruits = document.getElementsByClassName('fruit');
fruits.forEach(function(item) { /* ... */ }); // Ошибка!

Браузер обидится: "fruits.forEach is not a function".

4. Как превратить коллекцию в массив

Если вам нужно использовать методы массива (map, filter, forEach и т.д.), можно превратить коллекцию в настоящий массив. Для этого есть несколько способов:

Способ 1: Array.from

const fruits = document.getElementsByClassName('fruit');
const fruitsArray = Array.from(fruits);

fruitsArray.forEach(item => {
  item.style.background = 'lightyellow';
});

Способ 2: spread-оператор (...)

const fruits = document.getElementsByClassName('fruit');
const fruitsArray = [...fruits];

fruitsArray.map(item => {
  item.textContent = item.textContent.toUpperCase();
});

Способ 3: старый добрый Array.prototype.slice

const fruits = document.getElementsByClassName('fruit');
const fruitsArray = Array.prototype.slice.call(fruits);

fruitsArray.filter(item => item.textContent.includes('Яблоко'))
           .forEach(item => item.style.border = '2px solid red');

5. Практика: меняем стили и содержимое всех элементов

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

HTML:

<ul id="fruits">
  <li class="fruit">Яблоко</li>
  <li class="fruit">Груша</li>
  <li class="fruit">Банан</li>
</ul>
<button id="highlight">Подсветить все</button>

JavaScript:

const btn = document.getElementById('highlight');
const fruits = document.getElementsByClassName('fruit');

btn.addEventListener('click', function() {
  // Перебираем коллекцию через обычный for
  for (let i = 0; i < fruits.length; i++) {
    fruits[i].style.background = 'lightgreen';
  }
});

Теперь при нажатии на кнопку все пункты подсвечиваются зелёным!

А если хочется использовать forEach?

btn.addEventListener('click', function() {
  // Преобразуем коллекцию в массив и используем forEach
  Array.from(fruits).forEach(item => {
    item.style.background = 'lightgreen';
  });
});

6. Пример: динамическое добавление элементов и обновление коллекции

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

HTML:

<ul id="fruits">
  <li class="fruit">Яблоко</li>
  <li class="fruit">Груша</li>
  <li class="fruit">Банан</li>
</ul>
<input id="new-fruit" placeholder="Новый фрукт">
<button id="add-fruit">Добавить</button>
<button id="highlight">Подсветить все</button>

JavaScript:

const fruitsList = document.getElementById('fruits');
const input = document.getElementById('new-fruit');
const addBtn = document.getElementById('add-fruit');
const highlightBtn = document.getElementById('highlight');

addBtn.addEventListener('click', function() {
  const value = input.value.trim();
  if (value) {
    const li = document.createElement('li');
    li.className = 'fruit'; // не забываем класс!
    li.textContent = value;
    fruitsList.appendChild(li);
    input.value = '';
  }
});

// Теперь кнопка подсвечивает все элементы, включая добавленные
highlightBtn.addEventListener('click', function() {
  // fruits — всегда актуальная HTMLCollection!
  const fruits = document.getElementsByClassName('fruit');
  for (let i = 0; i < fruits.length; i++) {
    fruits[i].style.background = 'lightgreen';
  }
});

Важно:
Поскольку getElementsByClassName возвращает "живую" коллекцию, новые элементы автоматически попадают в неё. Если бы мы использовали querySelectorAll, пришлось бы каждый раз заново искать элементы.

7. Типичные ошибки при переборе коллекций и массивов

Ошибка №1: попытка использовать методы массива на коллекции напрямую.
Многие новички уверены, что если коллекция выглядит как массив, значит к ней можно применить map или forEach. Но для HTMLCollection это не так — получите "is not a function". Не забывайте преобразовывать коллекцию в массив!

Ошибка №2: путаница между живыми и неживыми коллекциями.
Добавили новый элемент, а он не появляется в NodeList? Или, наоборот, цикл по живой коллекции зациклился, потому что вы добавляете элементы внутри этого же цикла? Всегда учитывайте, как ведёт себя ваша коллекция.

Ошибка №3: забыли про индексацию с нуля.
И HTMLCollection, и NodeList, и массивы в JavaScript индексируются с нуля. Если начинаете с 1, потеряете первый элемент.

Ошибка №4: изменение коллекции во время перебора.
Если вы добавляете или удаляете элементы внутри цикла по живой коллекции, может возникнуть путаница: элементы перескакивают, длина меняется, часть элементов не обрабатывается. В таких случаях лучше сначала собрать массив нужных элементов, а потом работать с ним.

Ошибка №5: не проверяете, что коллекция не пуста.
Иногда методы поиска возвращают пустую коллекцию. Если сразу начать работать с fruits[0], получите undefined и, скорее всего, ошибку.

1
Задача
Модуль 1: Web Core, 22 уровень, 3 лекция
Недоступна
Задержка выполнения
Задержка выполнения
1
Задача
Модуль 1: Web Core, 22 уровень, 3 лекция
Недоступна
Параллельные задачи
Параллельные задачи
1
Опрос
Асинхронность, 22 уровень, 3 лекция
Недоступен
Асинхронность
Асинхронность
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ