← Назад к вопросам

Как мы работаем c DOM?

1.0 Junior🔥 151 комментариев
#JavaScript Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как мы работаем с DOM?

DOM (Document Object Model) - это представление HTML документа в виде дерева объектов, которым браузер позволяет манипулировать через JavaScript. Это фундаментальный навык фронтенд-разработчика. Я часто работаю с DOM, и знаю как делать это эффективно.

Основы: выбор элементов

// 1. getElementById - самый быстрый способ
const header = document.getElementById('header');

// 2. querySelector/querySelectorAll - универсальный способ
const mainDiv = document.querySelector('.main');
const buttons = document.querySelectorAll('button');

// 3. getElementsByClassName/getElementsByTagName - медленнее
const items = document.getElementsByClassName('item');
const paragraphs = document.getElementsByTagName('p');

Важное различие:

  • getElementById, querySelector, querySelectorAll возвращают статический результат
  • getElementsByClassName, getElementsByTagName возвращают живую коллекцию (обновляется при изменении DOM)
// Живая коллекция - опасно!
const items = document.getElementsByClassName('item');
// Если добавить элемент с классом item - коллекция обновится

// Статическая коллекция - безопаснее
const items = document.querySelectorAll('.item');
// Коллекция остаётся неизменной

Создание и удаление элементов

// Создание элемента
const newDiv = document.createElement('div');
newDiv.className = 'box';
newDiv.textContent = 'Hello';

// Добавление в DOM
const container = document.querySelector('.container');
container.appendChild(newDiv);

// Или более модерный способ
container.insertAdjacentHTML('beforeend', '<div class="box">Hello</div>');

// Удаление элемента
newDiv.remove();
// или старый способ
newDiv.parentElement.removeChild(newDiv);

Изменение свойств и атрибутов

const element = document.querySelector('.button');

// Свойства (properties)
element.textContent = 'Click me'; // текст
element.innerHTML = '<span>Click</span>'; // HTML
element.className = 'btn primary'; // CSS класс
element.style.color = 'red'; // встроенный стиль

// Атрибуты (attributes)
element.setAttribute('data-id', '123');
element.getAttribute('data-id'); // '123'
element.removeAttribute('disabled');
element.hasAttribute('disabled'); // false

// Или напрямую доступ к свойствам
element.id = 'my-button';
element.disabled = false;
element.dataset.id = '123'; // data-id="123"

Навигация по DOM дереву

const element = document.querySelector('.item');

// Родители
element.parentElement; // прямой родитель
element.parentNode; // может быть текстовый узел
element.closest('.container'); // ближайший родитель с классом container

// Дети
element.children; // только элементы
element.childNodes; // элементы и текстовые узлы
element.firstElementChild; // первый дочерний элемент
element.lastElementChild; // последний дочерний элемент

// Соседи
element.nextElementSibling; // следующий сосед
element.previousElementSibling; // предыдущий сосед

Работа с CSS классами

const button = document.querySelector('button');

// classList API (рекомендуется)
button.classList.add('active'); // добавить класс
button.classList.remove('disabled'); // удалить класс
button.classList.toggle('highlight'); // переключить класс
button.classList.contains('active'); // проверить наличие

// Несколько классов одновременно
button.classList.add('active', 'primary', 'large');

// Замена класса
button.classList.replace('old-class', 'new-class');

Обработка событий

const button = document.querySelector('button');

// Добавление слушателя
button.addEventListener('click', (event) => {
  console.log('Clicked!', event);
});

// Обработка разных событий
button.addEventListener('mouseover', (e) => {
  button.style.backgroundColor = 'blue';
});

button.addEventListener('mouseout', (e) => {
  button.style.backgroundColor = '';
});

// Удаление слушателя
const handleClick = (event) => {
  console.log('Click');
};

button.addEventListener('click', handleClick);
button.removeEventListener('click', handleClick);

// Делегирование событий (важно!)
const list = document.querySelector('.item-list');

list.addEventListener('click', (event) => {
  if (event.target.classList.contains('delete-btn')) {
    const item = event.target.closest('.item');
    item.remove();
  }
});

Практический пример: интерактивный список

class TodoList {
  constructor() {
    this.list = document.querySelector('.todo-list');
    this.input = document.querySelector('.todo-input');
    this.addBtn = document.querySelector('.add-btn');
    this.todos = [];
    
    this.setupListeners();
  }
  
  setupListeners() {
    this.addBtn.addEventListener('click', () => this.addTodo());
    this.input.addEventListener('keypress', (e) => {
      if (e.key === 'Enter') this.addTodo();
    });
    
    // Делегирование
    this.list.addEventListener('click', (e) => {
      if (e.target.classList.contains('delete')) {
        const id = e.target.closest('.todo-item').dataset.id;
        this.deleteTodo(id);
      }
    });
  }
  
  addTodo() {
    const text = this.input.value.trim();
    if (!text) return;
    
    const id = Date.now();
    const todo = { id, text, completed: false };
    this.todos.push(todo);
    
    const li = document.createElement('li');
    li.className = 'todo-item';
    li.dataset.id = id;
    li.innerHTML = `
      <span class="text">${text}</span>
      <button class="delete">Delete</button>
    `;
    
    this.list.appendChild(li);
    this.input.value = '';
  }
  
  deleteTodo(id) {
    this.todos = this.todos.filter(t => t.id !== id);
    const element = document.querySelector(`[data-id="${id}"]`);
    element.remove();
  }
}

const todoApp = new TodoList();

Производительность: batch обновления

// ПЛОХО - перерендер DOM много раз
for (let i = 0; i < 1000; i++) {
  const div = document.createElement('div');
  div.textContent = `Item ${i}`;
  document.body.appendChild(div); // Репринт каждый раз!
}

// ХОРОШО - один репринт
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
  const div = document.createElement('div');
  div.textContent = `Item ${i}`;
  fragment.appendChild(div);
}
document.body.appendChild(fragment); // Один репринт!

// ИЛИ использовать innerHTML
let html = '';
for (let i = 0; i < 1000; i++) {
  html += `<div>Item ${i}</div>`;
}
document.body.innerHTML = html;

Работа с стилями

const box = document.querySelector('.box');

// Установка стилей (избегай, это неэффективно)
box.style.width = '100px';
box.style.height = '100px';
box.style.backgroundColor = 'red';

// ЛУЧШЕ: используй CSS классы
box.classList.add('styled-box');

// CSS
/*
.styled-box {
  width: 100px;
  height: 100px;
  background-color: red;
}
*/

// Получение вычисленных стилей
const computedStyle = window.getComputedStyle(box);
console.log(computedStyle.backgroundColor);
console.log(computedStyle.width);

Условия содержимого (но в React это не нужно)

// Проверка наличия класса
if (button.classList.contains('active')) {
  console.log('Button is active');
}

// Проверка содержимого
if (button.textContent.includes('Submit')) {
  console.log('This is submit button');
}

// Проверка атрибута
if (button.hasAttribute('disabled')) {
  console.log('Button is disabled');
}

В современном фронтенде (React, Vue)

Важно: В React и Vue работа с DOM абстрагирована:

// React - не работаем с DOM напрямую
function MyButton() {
  const [isActive, setIsActive] = useState(false);
  
  return (
    <button
      className={isActive ? 'active' : ''}
      onClick={() => setIsActive(!isActive)}
    >
      Click me
    </button>
  );
}

// React сам управляет DOM, нам не нужно вызывать
// document.querySelector, appendChild и т.д.

Когда нужен прямой доступ к DOM:

// useRef для прямого доступа к элементу
function TextInput() {
  const inputRef = useRef(null);
  
  const focusInput = () => {
    inputRef.current.focus(); // Прямой доступ к DOM
  };
  
  return (
    <>
      <input ref={inputRef} />
      <button onClick={focusInput}>Focus</button>
    </>
  );
}

Частые ошибки

// ОШИБКА 1: использование innerHTML для пользовательского ввода (XSS!)
element.innerHTML = userInput; // Опасно!

// ИСПРАВИТЬ:
element.textContent = userInput; // Безопасно
// или санитизировать перед вставкой

// ОШИБКА 2: забыть что DOM живой
const items = document.getElementsByClassName('item');
for (let item of items) {
  item.remove(); // Может пропустить элементы!
}

// ИСПРАВИТЬ:
const items = Array.from(document.querySelectorAll('.item'));
for (let item of items) {
  item.remove();
}

// ОШИБКА 3: частые обращения в цикле
for (let i = 0; i < 1000; i++) {
  document.body.innerHTML += `<div>Item ${i}</div>`; // Медленно!
}

// ИСПРАВИТЬ: батч обновления
let html = '';
for (let i = 0; i < 1000; i++) {
  html += `<div>Item ${i}</div>`;
}
document.body.innerHTML = html; // Быстро

Итог

Я работаю с DOM через:

  • querySelector/querySelectorAll - выбор элементов
  • classList API - управление классами
  • addEventListener - обработка событий
  • Делегирование событий - для эффективности
  • DocumentFragment/innerHTML - для батч обновлений
  • В React/Vue - через state и refs, минимизируя прямой доступ

Самое важное - помнить, что DOM операции дорогие (вызывают repaint/reflow), поэтому их нужно минимизировать и батчить при возможности.

Как мы работаем c DOM? | PrepBro