Вставка: append, prepend, before, after
Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.
Разметку мало создать — её надо куда-то положить. Четыре метода — append, prepend, before, after — закрывают почти все случаи вставки, а заодно умеют переносить то, что уже стоит на странице.
Вокруг любого элемента есть ровно четыре места, куда можно что-то положить: перед ним, сразу за ним, в начало его содержимого и в конец. Под них и сделаны четыре метода. Запоминать нужно одно: before и after работают снаружи элемента, prepend и append — внутри.
| Вызов | Куда попадёт узел |
|---|---|
| elem.before(...) | снаружи, перед самим elem |
| elem.prepend(...) | внутрь, первым ребёнком |
| elem.append(...) | внутрь, последним ребёнком |
| elem.after(...) | снаружи, сразу после elem |
Обратите внимание: методы вызываются у самого элемента, а не у его родителя. Ссылка на parentElement не нужна — элемент и так знает, где стоит. Из той же семьи ещё двое: elem.replaceWith(...) заменяет элемент на переданное содержимое, а elem.remove() просто выкидывает его из документа. Им и клонированию отведена следующая статья.
Все четыре метода принимают сколько угодно аргументов, и каждый аргумент — либо узел, либо строка. Цикл со вставкой на каждой итерации не нужен: собрали массив элементов, развернули спредом — и одна вставка на всё.
Строка всегда становится текстом, а не разметкой. append("<b>жирный?</b>") не создаст элемент <b> — на странице появятся сами угловые скобки. Это не недоработка, а гарантия: подставляя в append чужой ник или комментарий, вы физически не можете внести в страницу чужие теги.
Узел живёт в документе в одном экземпляре. Если передать в append элемент, который уже где-то стоит, браузер не станет делать дубликат — он вынет узел со старого места и поставит на новое. Отдельного «вырезать» не существует, вставка сама и есть перемещение.
На этом держатся перетаскивание карточек между колонками и перенос строк таблицы — одна строчка кода вместо «удалить и создать заново». И переезжает узел целиком: вместе с обработчиками событий, введённым в поле текстом и позицией прокрутки. Пересоздание через innerHTML всё это теряет.
Тогда копию делают явно: node.cloneNode(true) вернёт новый узел вместе со всем содержимым (false — только сам элемент, без детей). Обработчики событий при клонировании не копируются — их вешают заново.
Иногда вставить нужно именно разметку — готовый кусок HTML строкой. Для этого есть elem.insertAdjacentHTML(позиция, строка). Позиций снова четыре, и это те же точки: "beforebegin" и "afterend" — снаружи элемента, "afterbegin" и "beforeend" — внутри, у открывающего и закрывающего тега.
Разница с append принципиальная: здесь строка разбирается как HTML со всеми последствиями. Если внутрь попадёт то, что прислал пользователь, вместе с текстом на страницу приедут и теги — например <img src="x" onerror="..."> выполнит чужой код, потому что событие error сработает сразу и никакой клик для этого не нужен. Правило простое: разметка — только своя, данные пользователя — через append или textContent.
Зато свою разметку insertAdjacentHTML добавляет аккуратнее, чем привычное elem.innerHTML += .... Второе перечитывает и пересоздаёт всё содержимое элемента: старые узлы умирают, а вместе с ними — обработчики событий, выделение текста и состояние полей. insertAdjacentHTML только дописывает новое, не трогая то, что уже стоит.
В коде постарше вставка выглядит иначе: методы вызываются у родителя и принимают ровно один узел. Знать их стоит не чтобы писать, а чтобы без запинки читать чужое.
| Старый способ | Современный аналог |
|---|---|
| parent.appendChild(node) | parent.append(node) |
| parent.insertBefore(node, ref) | ref.before(node) |
| parent.insertBefore(node, null) | parent.append(node) |
| parent.removeChild(node) | node.remove() |
| parent.replaceChild(fresh, old) | old.replaceWith(fresh) |
Одно отличие всё-таки важное: appendChild возвращает вставленный узел, а append — undefined. Строка вида const el = box.appendChild(document.createElement("li")) в чужом коде встречается и работает, но сломается, если механически заменить метод на append.
И ещё: старые методы принимают только узлы. Строку в appendChild не передать — сначала придётся сделать document.createTextNode("..."). Отсюда и берётся многословность старого кода: там, где сегодня хватает одного append с тремя аргументами, раньше выходил цикл на пять строк.
beforeиafterвставляют снаружи элемента,prependиappend— внутрь; ссылка на родителя не нужна.- Все четыре метода принимают сколько угодно аргументов: узлы и строки вперемешку, строка становится текстом, а не разметкой.
- Вставка узла, который уже в документе, переносит его вместе с обработчиками и состоянием; копия делается только через
cloneNode(true). insertAdjacentHTMLразбирает строку как HTML: годится для своей разметки, опасен для пользовательских данных, но бережнее, чемinnerHTML +=.appendChildиinsertBeforeделают то же самое на старый лад — у родителя, по одному узлу и только узлы.
1/3Что выведет этот код при разметке <div id="box"></div>?box.append("<b>hi</b>"); console.log(box.children.length);
2/3Элемент #tag лежит внутри #a. Выполнили b.append(tag). Что стало с #a?
3/3Куда попадёт разметка при elem.insertAdjacentHTML("afterbegin", html)?
Список одним вызовом, потом перестановка
Дан пустой список и массив покупок. Создайте по <li> на каждый пункт и вставьте их одним вызовом append. Затем переместите первый пункт в конец списка, не создавая новых элементов.
Массив строк превращаем в массив элементов через map и разворачиваем спредом — append примет их все сразу. Перестановка обходится без создания и удаления: append с уже стоящим узлом просто переносит его в конец.
Если бы вместо переноса вы сделали stack.append(stack.firstElementChild.cloneNode(true)), пунктов стало бы четыре — копия добавилась бы к оригиналу.
Обернуть кнопку, не потеряв обработчик
На кнопке уже висит обработчик клика. Оберните её в <div class="wrap">, не пересоздавая кнопку и не трогая innerHTML родителя. Проверка — вызов go.click() должен по-прежнему что-то печатать.
Два шага: поставить пустую обёртку на место кнопки через before, а потом затянуть кнопку внутрь через append. Второй вызов переносит существующий узел, поэтому обработчик остаётся на нём.
Порядок важен: если сначала положить кнопку в wrap, а wrap ещё не в документе, кнопка на время исчезнет со страницы. Практической разницы обычно нет, но привычка «сначала место, потом содержимое» избавляет от мигания в анимациях.