Удаление, замена и клонирование

узлы≈ 7 минут

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

Узел в документе — не строчка в файле, а живой объект: его можно вынуть, подменить другим или размножить копией. Разберём три глагола — remove, replaceWith, cloneNode — и заодно поймём, почему вставка узла в новое место его не копирует.

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

Метод remove() вынимает узел из документа. Родителя искать не нужно: узел знает его сам и отцепляется.

Только «удаление» здесь — слово громкое. Объект никуда не девается: если на него осталась ссылка в переменной, он живёт дальше со всем содержимым, просто больше не связан с документом. Проверить связь можно свойством isConnected.

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

Старый DOM умел менять детей только «руками родителя»: сначала найди parentNode, потом попроси его. Эти методы работают до сих пор и часто встречаются в чужом коде, но современные короче и читаются как фраза.

Через родителяСовременный способЧто делает
parent.removeChild(node)node.remove()убирает узел со страницы
parent.replaceChild(fresh, old)old.replaceWith(fresh)меняет узел на другой
parent.insertBefore(node, ref)ref.before(node)вставляет перед узлом
parent.appendChild(node)parent.append(node)добавляет в конец

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

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

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

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

cloneNode(true) делает глубокую копию — узел вместе со всеми потомками. Без аргумента (или с false) копия поверхностная: сам элемент с атрибутами, внутри пусто. Единицу забывают чаще всего, а потом ищут, куда делось содержимое.

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

Отсюда удобный приём: один готовый элемент в разметке служит образцом, а на страницу попадают его клоны с подменённым текстом. Структуру и классы не приходится собирать из createElement по кирпичику.

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

Атрибуты копируются все до одного, а id — обычный атрибут. В документе оказываются два элемента с одинаковым идентификатором, getElementById возвращает первый по порядку — и половина кода начинает работать «через раз». Правило: клонировали — переназначьте или снимите id до вставки.

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

Узел не бывает в двух местах сразу. Если вставить элемент, который уже на странице, браузер сначала молча вынет его из старого родителя, а потом положит на новое место. Отдельный remove() перед этим не нужен.

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

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

Внутри одного родителя то же самое: вставка ребёнка «в конец» к своему же родителю просто меняет порядок. Так сортируют списки, не пересоздавая элементы и не теряя их состояние.

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

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

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

Выхода два: вешать обработчик заново на каждую копию — или, что обычно лучше, слушать общего родителя и разбирать event.target. Тогда сколько угодно клонов работают сразу: слушателя у них и не должно быть. А вот атрибут onclick из разметки скопируется вместе с ней — он ведь атрибут; ещё один повод его не использовать.

И обратная сторона той же медали: при перемещении узла обработчики остаются при нём. Это тот же самый объект, у него просто сменился родитель.

  • node.remove() вынимает узел из документа, но не уничтожает: пока есть ссылка, узел цел и его можно вернуть на страницу.
  • old.replaceWith(...) подменяет узел одним или несколькими другими; строки в аргументах становятся текстовыми узлами.
  • cloneNode(true) — копия с потомками, cloneNode() — только сам узел с атрибутами; забытая единица и есть причина «пустых» копий.
  • Вставка узла, который уже на странице, перемещает его: старый родитель теряет ребёнка сам, без remove().
  • Клон получает все атрибуты (включая id — его лучше сразу переназначить), но не получает слушателей addEventListener; при перемещении обработчики остаются.

1/3Элемент note лежит в блоке a. Выполняем b.append(note). Сколько детей останется у a?

2/3Что вернёт card.cloneNode() без аргумента?

3/3К кнопке добавили обработчик через addEventListener, потом сделали copy = btn.cloneNode(true) и вставили копию на страницу. Сработает ли обработчик по клику на copy?

Убрать сделанное и подвинуть первое

В списке есть пункты с классом done. Уберите их все, а первый из оставшихся переместите в конец списка. Никаких createElement и innerHTML — только удаление и перемещение существующих узлов.

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

Удаляем по классу, затем вставляем первый оставшийся элемент в тот же список — вставка двигает узел, а не копирует его.

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

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

Функция duplicate

Напишите duplicate(el): она вставляет глубокую копию элемента сразу после оригинала и возвращает эту копию. Копия не должна принести с собой чужой id.

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

Клонируем глубоко, снимаем id и ставим копию сразу за оригиналом через after.

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

Важен порядок: id лучше убрать до вставки, иначе в документе на мгновение окажутся два одинаковых идентификатора. Если внутри копии есть вложенные элементы с id, их тоже придётся почистить — поможет copy.querySelectorAll("[id]").