W3docs

Манипуляция DOM в JavaScript

Изучите манипуляцию DOM в JavaScript: изменяйте содержимое через textContent и innerHTML, редактируйте атрибуты и классы, создавайте, вставляйте и удаляйте элементы.

Манипуляция DOM (Document Object Model) в JavaScript — фундаментальный навык для веб-разработчиков. DOM — это живое, древовидное представление страницы, которое браузер строит из вашего HTML; манипулировать им означает использовать JavaScript для чтения и изменения этого дерева во время выполнения, чтобы страница обновлялась без перезагрузки.

В этом руководстве рассматриваются четыре наиболее часто используемые операции:

  • Чтение и изменение содержимого с помощью textContent и innerHTML.
  • Чтение и изменение атрибутов с помощью getAttribute, setAttribute и classList.
  • Создание и вставка элементов с помощью createElement, append, insertBefore и insertAdjacentHTML.
  • Удаление и замена элементов с помощью remove() и replaceWith().

Прежде чем манипулировать элементом, его нужно найти. Если вы ещё не знакомы с getElementById, querySelector и связанными методами, сначала прочитайте Выбор элементов DOM и Поиск: getElement, querySelector — во всех примерах ниже предполагается, что у вас уже есть ссылка на узел.

Изменение содержимого и атрибутов элемента

Манипулирование содержимым и атрибутами элементов DOM — ключевой аспект динамической веб-разработки. Изменяя содержимое, мы обновляем текст или HTML внутри элемента. Изменяя атрибуты, мы модифицируем свойства, такие как class, id или src. JavaScript предоставляет мощные методы для выполнения этих задач, позволяя создавать отзывчивые, интерактивные веб-приложения. Рассмотрим, как эффективно использовать innerHTML, textContent, setAttribute и getAttribute.

Изменение содержимого элемента

Мы можем изменить содержимое элемента с помощью свойств innerHTML или textContent.

innerHTML vs textContent

innerHTML позволяет нам устанавливать или получать HTML-содержимое элемента, включая любые HTML-теги.

<!DOCTYPE html>
<html>
<head>
    <title>innerHTML vs textContent</title>
    <style>
        .content-container {
            margin: 20px;
            padding: 10px;
            border: 1px solid #ccc;
            border-radius: 5px;
        }
        .html-content {
            color: red;
        }
    </style>
</head>
<body>
    <div class="content-container">
        <p id="content">Original paragraph content.</p>
        <button id="change-innerHTML">Change using innerHTML</button>
        <button id="change-textContent">Change using textContent</button>
    </div>

    <script>
        const content = document.getElementById('content');
        const innerHTMLButton = document.getElementById('change-innerHTML');
        const textContentButton = document.getElementById('change-textContent');

        innerHTMLButton.addEventListener('click', () => {
            content.innerHTML = 'New content with <strong class="html-content">HTML</strong> tags.';
        });

        textContentButton.addEventListener('click', () => {
            content.textContent = 'Updated paragraph content without HTML tags.';
        });
    </script>
</body>
</html>

Пояснение:

  • innerHTML позволяет устанавливать или получать HTML-содержимое элемента, включая любые HTML-теги. В примере нажатие кнопки «Change using innerHTML» заменяет содержимое абзаца новым содержимым, содержащим HTML-теги, которые изменяют внешний вид текста.
  • textContent устанавливает или получает текстовое содержимое элемента без разбора HTML-тегов. Нажатие кнопки «Change using textContent» заменяет содержимое абзаца простым текстом, игнорируя любые HTML-теги.
Информация

Используйте textContent при вставке пользовательского контента, чтобы избежать угроз безопасности, таких как XSS (межсайтовый скриптинг). Присвоение строки от пользователя в innerHTML позволяет злоумышленнику внедрить <script> или атрибуты обработчиков событий, которые выполнятся на вашей странице. textContent никогда не разбирает HTML, поэтому строка отображается дословно и всегда безопасна.

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

// Replace the whole <p> with an <h2>, keeping it in the same position
const p = document.querySelector('p');
p.outerHTML = '<h2>A heading instead</h2>';
// After this, `p` still points at the old, detached node — re-query if you need the new one.

Изменение атрибутов элемента

Мы можем изменять атрибуты элемента с помощью метода setAttribute и получать их с помощью getAttribute. Эти методы полезны для динамического изменения элементов в ответ на действия пользователя или другие события.

<!DOCTYPE html>
<html>
<head>
    <title>setAttribute and getAttribute</title>
</head>
<body>
    <div id="container" class="initial-class">Container content</div>
    <button id="change-attribute">Change Attribute</button>
    <button id="get-attribute">Get Attribute</button>

    <script>
        const container = document.getElementById('container');
        const changeAttributeButton = document.getElementById('change-attribute');
        const getAttributeButton = document.getElementById('get-attribute');

        changeAttributeButton.addEventListener('click', () => {
            container.setAttribute('class', 'new-class');
            alert('Class attribute changed to "new-class"');
        });

        getAttributeButton.addEventListener('click', () => {
            const className = container.getAttribute('class');
            alert(`Current class attribute: ${className}`);
        });
    </script>
</body>
</html>

Пояснение:

  • setAttribute('attributeName', 'value'): этот метод позволяет установить новое значение для указанного атрибута элемента. В примере нажатие кнопки «Change Attribute» изменяет атрибут class элемента <div> с «initial-class» на «new-class».
  • getAttribute('attributeName'): этот метод получает текущее значение указанного атрибута. Нажатие кнопки «Get Attribute» показывает оповещение с текущим значением атрибута class элемента <div>.
Информация

Всегда используйте setAttribute и getAttribute для пользовательских и динамически генерируемых атрибутов. Для работы с классами предпочитайте API classList, описанный ниже — он редактирует классы по отдельности, а не перезаписывает всю строку class.

Атрибуты vs. свойства

Распространённый источник путаницы: HTML-атрибут (то, что записано в разметке) не всегда совпадает с DOM-свойством (живым значением объекта JavaScript). getAttribute('value') читает исходное значение из разметки, а input.value — то, что пользователь набрал в данный момент. Для boolean-атрибутов разница ещё более существенна: checkbox.getAttribute('checked') отражает разметку, а checkbox.checked — текущее состояние. Как правило: используйте свойства (el.id, el.value, el.checked) для стандартных, часто изменяемых значений, а setAttribute/getAttribute — для пользовательских data--атрибутов или разовых атрибутов разметки.

Для чтения и записи data-*-атрибутов существует специальный, удобный API — свойство dataset:

// <div id="card" data-user-id="42" data-role="admin"></div>
const card = document.getElementById('card');
console.log(card.dataset.userId); // "42" (note: data-user-id → userId)
card.dataset.role = 'editor';     // writes data-role="editor"

Управление классами с помощью classList

classList переключает отдельные классы, не затрагивая остальные:

element.classList.add('new-class');       // add one (or several) classes
element.classList.remove('old-class');    // remove a class
element.classList.toggle('active');       // add if absent, remove if present
element.classList.replace('open', 'shut'); // swap one class for another
console.log(element.classList.contains('active')); // true / false

toggle принимает необязательный второй аргумент для принудительной установки состояния, что удобно для синхронизации класса с условием: el.classList.toggle('valid', isValid). Для более глубокой работы со стилями — включая чтение и запись свойства style — смотрите Работа со стилями в DOM.

Добавление и удаление элементов

Мы можем добавлять новые элементы в DOM или удалять существующие.

Добавление элементов

Чтобы добавить новые элементы в DOM, сначала создаём их с помощью метода createElement, затем добавляем к существующему элементу через appendChild или вставляем в определённую позицию с помощью insertBefore.

createElement(), appendChild(), insertBefore()

<!DOCTYPE html>
<html>
<head>
    <title>Adding Elements</title>
</head>
<body>
    <div id="task-list">
        <h2>Task List</h2>
        <ul id="tasks">
            <li>Initial task</li>
        </ul>
        <input type="text" id="new-task" placeholder="New task">
        <button id="add-task">Add Task</button>
        <button id="insert-before">Insert Before First Task</button>
    </div>

    <script>
        const taskList = document.getElementById('tasks');
        const newTaskInput = document.getElementById('new-task');
        const addTaskButton = document.getElementById('add-task');
        const insertBeforeButton = document.getElementById('insert-before');

        addTaskButton.addEventListener('click', () => {
            const newTaskText = newTaskInput.value;
            if (newTaskText.trim()) {
                const newTask = document.createElement('li');
                newTask.textContent = newTaskText;
                taskList.appendChild(newTask);
                newTaskInput.value = '';
            }
        });

        insertBeforeButton.addEventListener('click', () => {
            const newTaskText = newTaskInput.value;
            if (newTaskText.trim()) {
                const newTask = document.createElement('li');
                newTask.textContent = newTaskText;
                const firstTask = taskList.firstElementChild;
                taskList.insertBefore(newTask, firstTask);
            }
        });
    </script>
</body>
</html>

Пояснение:

  • createElement('tagName'): этот метод создаёт новый элемент, указанный через tagName. Например, document.createElement('li') создаёт новый элемент <li>.
  • appendChild(newElement): этот метод добавляет новый дочерний элемент к указанному родительскому. В примере нажатие кнопки «Add Task» создаёт новый элемент списка (<li>) и добавляет его в список задач (<ul>).
  • insertBefore(newElement, referenceElement): этот метод вставляет новый элемент перед указанным опорным элементом внутри того же родителя. Нажатие кнопки «Insert Before First Task» создаёт новый элемент списка (<li>) и вставляет его перед первой задачей в списке.
Примечание

Для прямой вставки HTML-строк рассмотрите insertAdjacentHTML(). Для замены существующего элемента element.replaceWith(newElement) — современная альтернатива сочетанию remove() и appendChild().

insertAdjacentHTML и современные методы вставки

Когда у вас уже есть HTML-строка (а не узел, созданный через createElement), insertAdjacentHTML(position, html) разбирает и вставляет её за один вызов. Аргумент position — одно из четырёх ключевых слов, задающих позицию относительно элемента:

// Given:  <div id="box">content</div>
const box = document.getElementById('box');
box.insertAdjacentHTML('beforebegin', '<p>before the box</p>'); // before <div>
box.insertAdjacentHTML('afterbegin',  '<p>first child</p>');     // inside, at the start
box.insertAdjacentHTML('beforeend',   '<p>last child</p>');      // inside, at the end
box.insertAdjacentHTML('afterend',    '<p>after the box</p>');   // after </div>

Современные браузеры также предоставляют append(), prepend(), before() и after(), которые принимают как узлы, так и обычные строки и могут принимать несколько аргументов сразу — как правило, они нагляднее, чем appendChild/insertBefore:

const list = document.getElementById('tasks');
const item = document.createElement('li');
item.textContent = 'New task';
list.append(item, 'some trailing text'); // append node + string together
list.prepend('Top of the list');         // insert at the start

Клонирование элементов

Чтобы продублировать существующий узел, а не создавать его с нуля, используйте cloneNode(deep). Передайте true, чтобы скопировать элемент вместе со всеми его потомками; передайте false (или ничего), чтобы скопировать только сам элемент:

const template = document.getElementById('card');
const copy = template.cloneNode(true); // deep clone, including children
copy.id = 'card-2';                    // ids must stay unique
document.body.append(copy);

Для повторяющейся сложной разметки элемент <template> — специально созданный инструмент: его содержимое инертно до тех пор, пока вы не клонируете его.

Удаление элементов

Для удаления элемента можно использовать современный метод element.remove().

<!DOCTYPE html>
<html>
<head>
    <title>Removing Elements</title>
</head>
<body>
    <div id="container">
        <p id="paragraph">This is a paragraph.</p>
        <button id="remove-paragraph">Remove Paragraph</button>
    </div>

    <script>
        const container = document.getElementById('container');
        const paragraph = document.getElementById('paragraph');
        const removeParagraphButton = document.getElementById('remove-paragraph');

        removeParagraphButton.addEventListener('click', () => {
            paragraph.remove();
        });
    </script>
</body>
</html>

Пояснение:

  • element.remove(): этот современный метод удаляет указанный элемент из DOM напрямую. В примере нажатие кнопки «Remove Paragraph» удаляет элемент <p> со страницы.
Информация

Использование element.remove() — рекомендуемый подход для динамического изменения содержимого, например, удаления товаров из корзины: синтаксис чище, чем у устаревшего parent.removeChild(child) (который по-прежнему полезен, когда нужна ссылка на удалённый узел).

Эффективная вставка большого числа элементов

Каждый раз, когда вы вставляете узел в живой документ, браузер может пересчитывать разметку и перерисовывать страницу. Делать это в цикле — по одному элементу за итерацию — расточительно. DocumentFragment позволяет собрать узлы вне экрана и вставить их все за одну операцию:

const list = document.getElementById('tasks');
const fragment = document.createDocumentFragment();

for (let i = 1; i <= 1000; i++) {
  const li = document.createElement('li');
  li.textContent = `Task ${i}`;
  fragment.appendChild(li); // no layout work — fragment is not in the document
}

list.appendChild(fragment); // one insertion, one reflow

Два связанных правила помогают сохранять DOM-насыщенный код быстрым: группируйте чтение и запись (не чередуйте чтение offsetHeight с установкой стилей, иначе вы вынуждаете браузер к повторным перерасчётам), и предпочитайте формирование строки с последующим однократным присвоением innerHTML вместо многочисленных вызовов appendChild, когда к каждому узлу не нужно прикреплять обработчики событий. Подробное изложение см. в Оптимизация производительности DOM.

Пример: динамический список задач

Создадим простое приложение со списком задач, чтобы продемонстрировать описанные концепции.

<!DOCTYPE html>
<html>
<head>
    <title>To-Do List</title>
</head>
<body>
    <div id="todo-list">
        <h2>My To-Do List</h2>
        <ul id="tasks">
            <li>Learn JavaScript</li>
        </ul>
        <input type="text" id="new-task" placeholder="New task">
        <button id="add-task">Add Task</button>
    </div>

    <script>
        const taskList = document.getElementById('tasks');
        const newTaskInput = document.getElementById('new-task');
        const addTaskButton = document.getElementById('add-task');

        addTaskButton.addEventListener('click', () => {
            const newTaskText = newTaskInput.value;
            if (newTaskText.trim()) {
                const newTask = document.createElement('li');
                newTask.textContent = newTaskText;
                taskList.appendChild(newTask);
                newTaskInput.value = '';
            }
        });

        taskList.addEventListener('click', (event) => {
            if (event.target.tagName === 'LI') {
                event.target.remove();
            }
        });
    </script>
</body>
</html>

Пояснение:

  • createElement создаёт новый элемент списка для задачи, а appendChild добавляет его в список.
  • Один обработчик кликов прикреплён к <ul>, а не к каждому <li>. Поскольку клики всплывают от нажатого элемента к его предкам, обработчик проверяет event.target, чтобы определить, по какой задаче кликнули, и удаляет её. Этот паттерн называется делегированием событий и именно благодаря ему обработчик продолжает работать для задач, которых не существовало при загрузке страницы.
Примечание

Делегирование событий — вот почему мы не добавляем отдельный обработчик при каждом создании задачи: один обработчик на родителе охватывает как текущих, так и будущих потомков. Подробнее о всплытии, event.target и event.currentTarget, а также о делегировании читайте в Обработка событий в DOM.

Информация

Когда интерфейс разрастается до множества независимых, часто обновляемых фрагментов состояния, поддерживать DOM в актуальном состоянии вручную становится ненадёжно. Фреймворки, такие как React, Vue или Svelte, позволяют описать как должен выглядеть интерфейс для заданного состояния и самостоятельно управляют обновлениями DOM. Ручные техники из этой главы остаются фундаментом, на котором построены эти фреймворки.

Заключение

Владение манипуляцией DOM необходимо для создания динамичных и интерактивных веб-приложений. Теперь вы знаете, как изменять содержимое (textContent, innerHTML, outerHTML), работать с атрибутами и классами (setAttribute, dataset, classList), создавать и вставлять узлы (createElement, append, insertAdjacentHTML, cloneNode), удалять и заменять их (remove(), replaceWith()), а также группировать вставки с помощью DocumentFragment.

Для дальнейшего изучения:

Практика

Практика
Какой из следующих методов можно использовать для изменения содержимого элемента DOM?
Какой из следующих методов можно использовать для изменения содержимого элемента DOM?
Практика
Вам нужно безопасно отобразить комментарий пользователя внутри абзаца. Какое свойство следует использовать для присвоения?
Вам нужно безопасно отобразить комментарий пользователя внутри абзаца. Какое свойство следует использовать для присвоения?
Практика
В чём главное преимущество вставки множества новых элементов через DocumentFragment?
В чём главное преимущество вставки множества новых элементов через DocumentFragment?
Was this page helpful?