Удаление, замена и клонирование
Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.
Узел в документе — не строчка в файле, а живой объект: его можно вынуть, подменить другим или размножить копией. Разберём три глагола — remove, replaceWith, cloneNode — и заодно поймём, почему вставка узла в новое место его не копирует.
Метод remove() вынимает узел из документа. Родителя искать не нужно: узел знает его сам и отцепляется.
Только «удаление» здесь — слово громкое. Объект никуда не девается: если на него осталась ссылка в переменной, он живёт дальше со всем содержимым, просто больше не связан с документом. Проверить связь можно свойством isConnected.
Старый 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() ставит на место узла что-то другое. Аргументов может быть сколько угодно, и это не обязательно элементы: строка превращается в текстовый узел сама.
Частный случай — снять с текста обёртку, заменив элемент на его же содержимое. Соседние текстовые узлы при этом не склеиваются в один, но на вид и на textContent это никак не влияет.
cloneNode(true) делает глубокую копию — узел вместе со всеми потомками. Без аргумента (или с false) копия поверхностная: сам элемент с атрибутами, внутри пусто. Единицу забывают чаще всего, а потом ищут, куда делось содержимое.
Отсюда удобный приём: один готовый элемент в разметке служит образцом, а на страницу попадают его клоны с подменённым текстом. Структуру и классы не приходится собирать из createElement по кирпичику.
Атрибуты копируются все до одного, а id — обычный атрибут. В документе оказываются два элемента с одинаковым идентификатором, getElementById возвращает первый по порядку — и половина кода начинает работать «через раз». Правило: клонировали — переназначьте или снимите id до вставки.
Узел не бывает в двух местах сразу. Если вставить элемент, который уже на странице, браузер сначала молча вынет его из старого родителя, а потом положит на новое место. Отдельный remove() перед этим не нужен.
Удаление и вставка — почти одно и то же действие с разным финалом: узел всегда сначала отцепляется от старого родителя, а дальше либо остаётся в вашей переменной, либо едет в новое место.
Внутри одного родителя то же самое: вставка ребёнка «в конец» к своему же родителю просто меняет порядок. Так сортируют списки, не пересоздавая элементы и не теряя их состояние.
Клонируется разметка, а не жизнь узла. Обработчики, повешенные через addEventListener, — не атрибут, а связь конкретного объекта с функцией, и в копию они не переезжают. Клон выглядит точно так же и молчит в ответ на клик.
Выхода два: вешать обработчик заново на каждую копию — или, что обычно лучше, слушать общего родителя и разбирать 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 — только удаление и перемещение существующих узлов.
Удаляем по классу, затем вставляем первый оставшийся элемент в тот же список — вставка двигает узел, а не копирует его.
querySelectorAll возвращает статический список, поэтому удалять элементы прямо в цикле безопасно. С живой коллекцией вроде getElementsByClassName так делать нельзя: она меняется под ногами, и часть элементов будет пропущена.
Функция duplicate
Напишите duplicate(el): она вставляет глубокую копию элемента сразу после оригинала и возвращает эту копию. Копия не должна принести с собой чужой id.
Клонируем глубоко, снимаем id и ставим копию сразу за оригиналом через after.
Важен порядок: id лучше убрать до вставки, иначе в документе на мгновение окажутся два одинаковых идентификатора. Если внутри копии есть вложенные элементы с id, их тоже придётся почистить — поможет copy.querySelectorAll("[id]").