Вставка: append, prepend, before, after

узлы≈ 7 минут

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

Разметку мало создать — её надо куда-то положить. Четыре метода — append, prepend, before, after — закрывают почти все случаи вставки, а заодно умеют переносить то, что уже стоит на странице.

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

Вокруг любого элемента есть ровно четыре места, куда можно что-то положить: перед ним, сразу за ним, в начало его содержимого и в конец. Под них и сделаны четыре метода. Запоминать нужно одно: before и after работают снаружи элемента, prepend и append — внутри.

ВызовКуда попадёт узел
elem.before(...)снаружи, перед самим elem
elem.prepend(...)внутрь, первым ребёнком
elem.append(...)внутрь, последним ребёнком
elem.after(...)снаружи, сразу после elem
js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Обратите внимание: методы вызываются у самого элемента, а не у его родителя. Ссылка на parentElement не нужна — элемент и так знает, где стоит. Из той же семьи ещё двое: elem.replaceWith(...) заменяет элемент на переданное содержимое, а elem.remove() просто выкидывает его из документа. Им и клонированию отведена следующая статья.

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

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

Строка всегда становится текстом, а не разметкой. append("<b>жирный?</b>") не создаст элемент <b> — на странице появятся сами угловые скобки. Это не недоработка, а гарантия: подставляя в append чужой ник или комментарий, вы физически не можете внести в страницу чужие теги.

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

Узел живёт в документе в одном экземпляре. Если передать в append элемент, который уже где-то стоит, браузер не станет делать дубликат — он вынет узел со старого места и поставит на новое. Отдельного «вырезать» не существует, вставка сама и есть перемещение.

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

На этом держатся перетаскивание карточек между колонками и перенос строк таблицы — одна строчка кода вместо «удалить и создать заново». И переезжает узел целиком: вместе с обработчиками событий, введённым в поле текстом и позицией прокрутки. Пересоздание через innerHTML всё это теряет.

Тогда копию делают явно: node.cloneNode(true) вернёт новый узел вместе со всем содержимым (false — только сам элемент, без детей). Обработчики событий при клонировании не копируются — их вешают заново.

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

Иногда вставить нужно именно разметку — готовый кусок HTML строкой. Для этого есть elem.insertAdjacentHTML(позиция, строка). Позиций снова четыре, и это те же точки: "beforebegin" и "afterend" — снаружи элемента, "afterbegin" и "beforeend" — внутри, у открывающего и закрывающего тега.

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

Разница с append принципиальная: здесь строка разбирается как HTML со всеми последствиями. Если внутрь попадёт то, что прислал пользователь, вместе с текстом на страницу приедут и теги — например <img src="x" onerror="..."> выполнит чужой код, потому что событие error сработает сразу и никакой клик для этого не нужен. Правило простое: разметка — только своя, данные пользователя — через append или textContent.

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

Зато свою разметку insertAdjacentHTML добавляет аккуратнее, чем привычное elem.innerHTML += .... Второе перечитывает и пересоздаёт всё содержимое элемента: старые узлы умирают, а вместе с ними — обработчики событий, выделение текста и состояние полей. insertAdjacentHTML только дописывает новое, не трогая то, что уже стоит.

В коде постарше вставка выглядит иначе: методы вызываются у родителя и принимают ровно один узел. Знать их стоит не чтобы писать, а чтобы без запинки читать чужое.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера
Старый способСовременный аналог
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. Затем переместите первый пункт в конец списка, не создавая новых элементов.

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

Массив строк превращаем в массив элементов через map и разворачиваем спредом — append примет их все сразу. Перестановка обходится без создания и удаления: append с уже стоящим узлом просто переносит его в конец.

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

Если бы вместо переноса вы сделали stack.append(stack.firstElementChild.cloneNode(true)), пунктов стало бы четыре — копия добавилась бы к оригиналу.

Обернуть кнопку, не потеряв обработчик

На кнопке уже висит обработчик клика. Оберните её в <div class="wrap">, не пересоздавая кнопку и не трогая innerHTML родителя. Проверка — вызов go.click() должен по-прежнему что-то печатать.

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

Два шага: поставить пустую обёртку на место кнопки через before, а потом затянуть кнопку внутрь через append. Второй вызов переносит существующий узел, поэтому обработчик остаётся на нём.

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

Порядок важен: если сначала положить кнопку в wrap, а wrap ещё не в документе, кнопка на время исчезнет со страницы. Практической разницы обычно нет, но привычка «сначала место, потом содержимое» избавляет от мигания в анимациях.