JavaScript: DOM, события и запросы к серверу
Поиск и изменение элементов страницы, обработка событий и делегирование, работа с массивами объектов, асинхронные запросы через fetch, валидация формы.
JavaScript в курсовой почти всегда сводится к трём задачам: найти элементы страницы, отреагировать на действия пользователя и обменяться данными с сервером. Разберём все три.
Поиск и изменение элементов
// Поиск: querySelector принимает любой CSS-селектор
const form = document.querySelector('#order-form');
const items = document.querySelectorAll('.item'); // NodeList
// Изменение содержимого
const title = document.querySelector('h1');
title.textContent = 'Новый заголовок'; // безопасно: только текст
title.innerHTML = '<em>с разметкой</em>'; // осторожно: возможен XSS
// Классы и стили
title.classList.add('active');
title.classList.toggle('hidden');
title.classList.contains('active'); // true
// Атрибуты и data-атрибуты
const btn = document.querySelector('button');
btn.disabled = true;
btn.dataset.productId = '42'; // data-product-id="42"
// Создание элемента
const li = document.createElement('li');
li.textContent = 'Новая позиция';
document.querySelector('ul').append(li);
События
const button = document.querySelector('#add');
button.addEventListener('click', (event) => {
console.log('нажата кнопка', event.target);
});
// Отправка формы: отменяем перезагрузку страницы
form.addEventListener('submit', (event) => {
event.preventDefault();
const data = new FormData(form);
console.log(Object.fromEntries(data));
});
// Делегирование: один обработчик на весь список
document.querySelector('#list').addEventListener('click', (event) => {
const btn = event.target.closest('.delete');
if (!btn) return; // клик мимо кнопки
btn.closest('li').remove();
});
// Ввод с задержкой, чтобы не дёргать сервер на каждую букву
let timer;
searchInput.addEventListener('input', (e) => {
clearTimeout(timer);
timer = setTimeout(() => search(e.target.value), 300);
});
Делегирование решает главную проблему динамических списков: элементы, добавленные после навешивания обработчиков, всё равно работают, потому что слушатель висит на неизменном родителе.
Массивы объектов
const products = [
{ id: 1, name: 'Резистор', price: 5.5, qty: 100 },
{ id: 2, name: 'Конденсатор', price: 12, qty: 50 },
{ id: 3, name: 'Транзистор', price: 45, qty: 20 },
];
const expensive = products.filter(p => p.price > 10);
const names = products.map(p => p.name);
const total = products.reduce((sum, p) => sum + p.price * p.qty, 0);
const found = products.find(p => p.id === 2);
const sorted = [...products].sort((a, b) => b.price - a.price);
// Рендер таблицы
function render(list) {
const tbody = document.querySelector('tbody');
tbody.innerHTML = '';
for (const p of list) {
const tr = document.createElement('tr');
tr.innerHTML = `<td></td><td></td><td></td>`;
tr.children[0].textContent = p.name;
tr.children[1].textContent = p.price.toFixed(2);
tr.children[2].textContent = p.qty;
tbody.append(tr);
}
}
Запросы к серверу
async function loadProducts() {
try {
const response = await fetch('/api/products');
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
render(data);
} catch (err) {
showError('Не удалось загрузить данные: ' + err.message);
}
}
async function sendOrder(payload) {
const response = await fetch('/api/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
return response.json();
}
Важная деталь: fetch не выбрасывает исключение на код 404 или 500 — он считает ответ полученным. Проверка response.ok обязательна, иначе ошибка сервера пройдёт незамеченной.
Валидация формы
function validate(form) {
const errors = [];
const name = form.name.value.trim();
const email = form.email.value.trim();
const qty = Number(form.qty.value);
if (name.length < 2) errors.push('Укажите имя');
if (!/^[^@\s]+@[^@\s]+\.[a-z]{2,}$/i.test(email)) errors.push('Некорректный e-mail');
if (!Number.isInteger(qty) || qty < 1) errors.push('Количество — целое число от 1');
return errors;
}
form.addEventListener('submit', (e) => {
e.preventDefault();
const errors = validate(form);
if (errors.length) {
showError(errors.join('. '));
return;
}
sendOrder(Object.fromEntries(new FormData(form)));
});
Хранение данных в браузере
// localStorage хранит только строки
localStorage.setItem('cart', JSON.stringify(products));
const cart = JSON.parse(localStorage.getItem('cart') || '[]');
localStorage.removeItem('cart');
// sessionStorage живёт до закрытия вкладки
Частые ошибки
- Скрипт подключён в head без defer — элементы ещё не созданы, querySelector возвращает null.
- Забыт event.preventDefault() у формы — страница перезагружается, и результат работы теряется.
- Сравнение через == вместо ===: строка «0» неожиданно оказывается равной нулю.
- Обработчики навешены в цикле на каждый элемент вместо делегирования — при обновлении списка перестают работать.
- this внутри обычной функции не тот, что ожидается: используйте стрелочные функции внутри методов.
Частые вопросы
Чем отличается querySelector от getElementById?
querySelector принимает любой CSS-селектор и возвращает первое совпадение, getElementById ищет только по id и работает немного быстрее. На практике разница в скорости незаметна, поэтому обычно берут querySelector.
Почему нужен defer у скрипта?
Без него браузер выполняет скрипт сразу при встрече, до построения остальной страницы, и обращения к элементам возвращают null. defer откладывает выполнение до полной загрузки разметки.
Что лучше: fetch или XMLHttpRequest?
fetch: он основан на промисах, работает с async/await и читается заметно короче. XMLHttpRequest остаётся в старом коде и там, где нужно отслеживать прогресс загрузки файла.