До сих пор JavaScript жил в консоли: мы считали, фильтровали и выводили текст. Сегодня он выходит на страницу. Каталог товаров, корзина, список задач, оценки в личном кабинете — всё это на настоящих сайтах рисует JavaScript, который находит нужное место на странице и меняет его. Сегодня вы соберёте страницу, в которой весь контент создаётся из массива.
Объяснить, что такое DOM и чем он отличается от HTML-файла
Находить элементы через querySelector и querySelectorAll
Менять текст, разметку, классы, атрибуты и data-*
Создавать элементы через createElement, вставлять их и удалять
Рисовать список карточек из массива объектов и очищать его
Делить работу с ИИ: он пишет разметку и стили, вы — логику
Как работать с этой страницей
В этом уроке много песочниц. В каждой три вкладки: index.html (разметка), style.css (стили, если есть) и script.js (ваш код). Нажмите ▶ Запустить или Ctrl + Enter — ниже появится живая страница, а под ней консоль. Задачи проверяются по тому, что вы вывели через console.log, поэтому оставляйте строки с выводом на месте. Подсказки спрятаны: сначала попробуйте сами.
Одно правило песочницы: ваша разметка стоит в <body> не одна — после неё браузер добавляет служебные <script>. Поэтому мы всегда кладём содержимое в одну обёртку или ищем элементы по селектору, а не считаем детей у body.
01 Что такое DOM
Вы пишете HTML — это обычный текст в файле. Когда браузер открывает страницу, он читает этот текст и строит из него дерево объектов. Это дерево называется DOM — Document Object Model, «объектная модель документа». Каждый тег превращается в объект, у которого есть свойства (текст, классы, размеры) и методы. Именно с этими объектами работает JavaScript.
Аналогия из жизни
HTML-файл — это напечатанное меню в папке. DOM — это накрытый стол в чойхоне, который строится по этому меню. Официант (JavaScript) может поставить на стол ещё одну самбусу, убрать пустую пиалу или поменять чай. Напечатанное меню в папке от этого не меняется. Обновили страницу — стол накрывается заново по меню.
Вот простая страница и дерево, которое из неё получается:
Тег внутри тега — это «ребёнок» внутри «родителя». Текст тоже узел дерева.
document
Корень всего дерева и «входная дверь» в страницу. Через него вы ищете элементы: document.querySelector(...).
Элемент
Объект, соответствующий тегу: <h1>, <li>, <div>. Именно их мы будем искать, менять, создавать и удалять.
Родитель и дети
<ul> — родитель двух <li>. Два <li> — соседи (siblings).
Текстовый узел
Кусочек текста внутри элемента. Пробелы и переносы строк между тегами — тоже текстовые узлы.
Как посмотреть DOM своими глазами
Откройте любой сайт, нажмите F12 и перейдите на вкладку Elements. Это и есть живой DOM: вы видите дерево, можете кликать по элементам, менять текст двойным щелчком, перетаскивать теги — и страница реагирует сразу. Сам файл при этом не меняется. Выделенный в Elements элемент доступен в консоли как $0.
Ходим по дереву
У каждого элемента есть «стрелки» к соседним узлам. Они пригодятся, когда один элемент найден и нужно добраться до соседей:
// body.firstElementChild — первый элемент нашей разметки, то есть <ul>const menu = document.body.firstElementChild;
console.log(menu.tagName); // UL
console.log(menu.children.length); // 3
console.log(menu.firstElementChild.textContent); // Самбуса
console.log(menu.lastElementChild.textContent); // Шакаробconst second = menu.children[1]; // дети лежат как в массиве: 0, 1, 2
console.log(second.textContent); // Курутоб
console.log(second.previousElementSibling.textContent); // Самбуса
console.log(second.parentElement.tagName); // UL
Предпросмотр
Нажмите «Запустить», чтобы увидеть результат.
textContent — это текст внутри элемента. Подробнее про него — в третьем разделе, пока просто пользуйтесь.
Частая ошибка: childNodes вместо children
Есть похожее свойство childNodes. Оно возвращает все узлы, включая пробелы и переносы строк между тегами. Поэтому «детей» оказывается больше, чем вы видите глазами. Для работы с элементами используйте children и семейство ...ElementChild, ...ElementSibling.
childNodes считает и пробелы · Ctrl + Enter
const box = document.createElement("div");
box.innerHTML = `
<p>Один</p>
<p>Два</p>
`;
console.log(box.children.length); // → 2 — два абзаца
console.log(box.childNodes.length); // → 5 — два абзаца и три «пустых» текстовых узла
const list = document.body.firstElementChild; // это <ul>// 1) сколько детей у списка// 2) текст первого ребёнка// 3) текст последнего ребёнка
Предпросмотр
Нажмите «Запустить», чтобы увидеть результат.
Ожидаемый результат
4
Нон
Мева
Подсказка 1
Количество детей — list.children.length.
Подсказка 2
Первый ребёнок — list.firstElementChild, последний — list.lastElementChild. Текст берём через .textContent. Каждое значение — отдельный console.log.
ЗАДАЧА 1.2
Предскажите вывод
лёгкая
Сначала не запуская код, запишите в тетрадь, что выведут пять строк. Потом запустите и сверьтесь. После этого добавьте шестую строку: выведите текст абзаца «Преподаватель…», но двигаясь от конца — через group.lastElementChild.previousElementSibling.
Выведите в консоль: сколько у списка childNodes, сколько children, а затем текст каждого города — каждый на своей строке (используйте for...of по list.children). Подумайте, откуда взялась разница в первых двух числах.
const list = document.body.firstElementChild;
// childNodes.length// children.length// цикл for...of по list.children — вывести текст каждого города
Предпросмотр
Нажмите «Запустить», чтобы увидеть результат.
Ожидаемый результат
7
3
Душанбе
Худжанд
Бохтар
Подсказка 1
Между тегами в разметке есть переносы строк и отступы. Каждый такой кусочек — отдельный текстовый узел: три перед <li> и один после последнего. Итого 3 элемента + 4 текстовых узла = 7.
Подсказка 2
for (const li of list.children) { console.log(li.textContent); }
02 Поиск элементов: querySelector
Чтобы что-то изменить на странице, сначала нужно это «что-то» найти. Для поиска в современном JavaScript почти всегда хватает двух методов. Главное: в них вы пишете обычный CSS-селектор — тот же, что в файле стилей. Новый язык учить не нужно.
document.querySelector(селектор)
Возвращает первый подходящий элемент. Если ничего не нашлось — null.
document.querySelectorAll(селектор)
Возвращает список всех подходящих элементов (NodeList). Если ничего нет — пустой список, не null.
const title = document.querySelector("#title"); // по idconst first = document.querySelector(".item"); // по классу — первый из трёхconst hot = document.querySelector(".item.hot"); // два класса сразуconst all = document.querySelectorAll(".item"); // все три
console.log(title.textContent); // Чайхона «Сомон»
console.log(first.textContent); // Курутоб
console.log(hot.textContent); // Шакароб
console.log(all.length); // 3
Предпросмотр
Нажмите «Запустить», чтобы увидеть результат.
Аналогия из жизни
Вы стоите на Зелёном базаре и говорите: «Найдите мне самую первую палатку с курагой» — это querySelector. А «Покажите все палатки с курагой» — это querySelectorAll. Селектор — это то, как вы описываете палатку: по номеру (#id), по вывеске (.class) или по типу (li).
Какие селекторы можно писать
Селектор
Что находит
"#title"
элемент с id="title"
".item"
элементы с классом item
"li"
все теги <li>
".menu li"
<li> внутри .menu (на любой глубине)
".menu > li"
<li> — прямые дети .menu
"li:first-child", "li:nth-child(2)"
первый и второй по счёту
"[data-id='17']"
по атрибуту
"input[type='text']"
тег и атрибут вместе
Совет
Если не уверены, правильный ли селектор, проверьте его в DevTools: на вкладке Elements нажмите Ctrl + F и вставьте селектор. Браузер подсветит найденное и покажет, сколько совпадений.
NodeList: похож на массив, но не массив
querySelectorAll возвращает NodeList. У него есть .length, к элементам можно обратиться по индексу, его можно перебирать через for...of и forEach. Но методов map, filter и reduce у него нет. Если нужны — превращаем в настоящий массив:
Метод querySelector есть не только у document, но и у любого элемента. Тогда поиск идёт только внутри него. Это важно, когда на странице много одинаковых карточек и нужно достать цену именно из этой карточки:
const cards = document.querySelectorAll(".card");
for (const card of cards) {
const name = card.querySelector(".name").textContent; // ищем только внутри cardconst price = card.querySelector(".price").textContent;
console.log(name + ": " + price);
}
Предпросмотр
Нажмите «Запустить», чтобы увидеть результат.
Есть и старые методы: getElementById("title"), getElementsByClassName, getElementsByTagName. Они работают, и вы встретите их в чужом коде, но querySelector универсальнее — достаточно помнить один способ.
Частая ошибка: элемент не найден (null)
Если querySelector ничего не нашёл, он возвращает null. А у null нет ни textContent, ни других свойств — получаем самую знаменитую ошибку DOM. Причин обычно три: опечатка в селекторе, забытая точка или решётка ("item" вместо ".item"), либо скрипт запустился раньше, чем элемент появился на странице.
Этот код выдаст ошибку · Ctrl + Enter
const price = document.querySelector(".price"); // на странице такого элемента нет → null
console.log(price); // → null
console.log(price.textContent); // ✖ TypeError: Cannot read properties of null
Про третью причину: в настоящем проекте подключайте скрипт с атрибутом defer — тогда он выполняется после того, как браузер построил всё дерево. В песочницах урока это уже сделано за вас.
index.html — как подключать скрипт в реальном проекте
Найденные элементы кладите в переменные один раз в начале скрипта: const list = document.querySelector("#list");. Не ищите один и тот же элемент по десять раз — код станет короче, а браузер быстрее.
// 1) текст заголовка по id// 2) текст первого .item// 3) сколько всего .item
Предпросмотр
Нажмите «Запустить», чтобы увидеть результат.
Ожидаемый результат
Чайхона «Сомон»
Курутоб
3
Подсказка 1
Заголовок: document.querySelector("#title"). Решётка — для id, точка — для класса.
Подсказка 2
Первый .item — querySelector(".item"), все — querySelectorAll(".item") и затем .length.
ЗАДАЧА 2.2
Найди три бага в селекторах
средняя
Код хочет вывести заголовок, количество товаров и текст товара со скидкой, но ничего не работает. Запустите его, прочитайте ошибку в консоли и исправьте три места, помеченные номерами. Исправляйте по одному и каждый раз запускайте — ошибки открываются по очереди.
const title = document.querySelector(".title"); // 1const items = document.querySelector(".item"); // 2const sale = document.querySelector("#sale"); // 3
console.log(title.textContent);
console.log(items.length);
console.log(sale.textContent);
Предпросмотр
Нажмите «Запустить», чтобы увидеть результат.
Ожидаемый результат
Корзина
3
Зардолу — 25
Подсказка 1
Бага 1: у заголовка не класс, а id — значит, нужна решётка. Бага 3 — наоборот: sale — это класс, а не id.
Подсказка 2
Баг 2: querySelector вернёт один элемент, а у него нет .length. Чтобы посчитать все товары, нужен querySelectorAll.
ЗАДАЧА 2.3
Цены из карточек
сложная
В каталоге четыре карточки. Переберите их, достаньте цену и название из каждой (поиск внутри карточки), посчитайте общую сумму и найдите самую дорогую. Цена в textContent — строка, превращайте её в число через Number(...).
const cards = document.querySelectorAll(".card");
let total = 0;
let maxPrice = 0;
let maxName = "";
// переберите cards: достаньте name и price, обновите total, maxPrice, maxName
console.log("Итого: " + total + " сомони");
console.log("Самый дорогой: " + maxName);
Предпросмотр
Нажмите «Запустить», чтобы увидеть результат.
Ожидаемый результат
Итого: 155 сомони
Самый дорогой: Шапка
Подсказка 1
Цикл for (const card of cards) { ... }. Внутри: const price = Number(card.querySelector(".price").textContent); и такая же строка для имени.
Подсказка 2
Сумма: total += price. Если price > maxPrice, запишите новую цену в maxPrice и имя — в maxName.
03 Меняем содержимое: textContent и innerHTML
Элемент найден. Теперь можно прочитать то, что внутри него, и заменить. Для этого у каждого элемента есть два главных свойства. Они похожи, но делают разное — и разница важна не только для красоты, но и для безопасности.
textContent — только текст
Читает и записывает обычный текст. Любые < и > считаются просто символами, теги не создаются. Быстро и безопасно.
innerHTML — текст и разметка
Читает и записывает HTML-строку. Теги внутри превращаются в настоящие элементы. Мощно, но опасно, если в строке чужой текст.
const dish = document.querySelector("#dish");
const box = document.querySelector("#box");
dish.textContent = "Оши палав"; // заменили текст
console.log(dish.textContent); // Оши палав
box.innerHTML = "<b>Цена:</b> 35 сомони"; // тег <b> станет настоящим элементом
console.log(box.children.length); // 1 — внутри появился <b>
console.log(box.innerHTML); // <b>Цена:</b> 35 сомони
console.log(box.textContent); // Цена: 35 сомони — только текст, без тегов
Предпросмотр
Нажмите «Запустить», чтобы увидеть результат.
Аналогия из жизни
Представьте, что вы заказываете торт с надписью. textContent — это когда кондитер пишет на торте ровно то, что вы сказали, даже если вы продиктовали странные значки. innerHTML — когда вы даёте кондитеру инструкцию: «поставь здесь украшение, а вот тут сделай жирную надпись». Он выполнит инструкцию буквально. Если инструкцию написал посторонний человек — он может заставить кондитера сделать что угодно.
Читаем и пишем
el.textContent — прочитать весь текст внутри элемента (включая вложенные теги, но без самих тегов).
el.textContent = "новый текст" — заменить всё содержимое одним текстом.
el.innerHTML — прочитать содержимое вместе с тегами, как строку.
el.innerHTML = "<b>жирный</b>" — заменить содержимое, разобрав строку как HTML.
Есть ещё innerText: он учитывает CSS и не показывает скрытый текст. Для обычной работы берите textContent — он проще и предсказуемее.
Почему innerHTML опасен
Допустим, на вашем сайте пользователь пишет комментарий, а вы вставляете его на страницу через innerHTML. Если вместо «Отличный курс!» он напишет специально подготовленный тег <img> со скрытым кодом — браузер создаст этот элемент и выполнит чужой код на страницах других посетителей. Такая атака называется XSS и входит в число самых распространённых в вебе.
Правило безопасности
Всё, что пришло от пользователя, из формы, из адресной строки или от внешнего API, вставляйте через textContent. innerHTML — только для разметки, которую написали вы сами (или ИИ, и вы её проверили). Посмотрите, как выглядит разница:
Можно редактировать · Ctrl + Enter
const comment = "Круто! <b>Пять звёзд</b>"; // представим, что это ввёл пользовательconst safe = document.createElement("div");
safe.textContent = comment; // текст остался текстом
console.log(safe.children.length); // → 0 — никаких тегов не появилосьconst risky = document.createElement("div");
risky.innerHTML = comment; // строка разобрана как HTML
console.log(risky.children.length); // → 1 — появился настоящий тег <b>
Ловушка: += для innerHTML
Запись list.innerHTML += "<li>…</li>" выглядит как «добавить в конец», но на самом деле браузер сносит весь список и собирает заново. Все ранее найденные элементы в ваших переменных теряют связь со страницей.
Что происходит при += для innerHTML · Ctrl + Enter
const list = document.createElement("ul");
list.innerHTML = "<li>Нон</li>";
const first = list.firstElementChild; // запомнили первый пункт
list.innerHTML += "<li>Чой</li>"; // «добавили» второй
console.log(list.children.length); // → 2
console.log(first === list.firstElementChild); // → false — это уже другой, новый элемент!
console.log(list.contains(first)); // → false — старый пункт выброшен
В разделе 5 вы узнаете, как добавлять элементы без такого побочного эффекта — через createElement и append.
Собираем список строкой
Если разметку пишете вы сами, а данные безопасные (ваш массив, а не ввод пользователя), удобно собирать HTML шаблонными строками и методами массивов, которые вы уже знаете:
Страница показывает заглушки «Загрузка…». Замените текст заголовка на Курутоб, а текст цены — на 35 сомони через textContent. После замены выведите оба текста в консоль.
const title = document.querySelector("#title");
const price = document.querySelector(".price");
// замените тексты
console.log(title.textContent);
console.log(price.textContent);
Предпросмотр
Нажмите «Запустить», чтобы увидеть результат.
Ожидаемый результат
Курутоб
35 сомони
Подсказка 1
Запись в свойство — это присваивание: title.textContent = "...";
Подсказка 2
Две строки между поиском и console.log: title.textContent = "Курутоб"; и price.textContent = "35 сомони";
ЗАДАЧА 3.2
Безопасный вывод имени
средняя
Строка userName — это «ввод пользователя». Положите её в блок #a через innerHTML, а в блок #b — через textContent. Посмотрите на preview: в каком блоке видны теги? Консоль подтвердит разницу.
Две вставки · Ctrl + Enter
<divid="a"></div><divid="b"></div>
const userName = "Ali <b>Pro</b>";
const a = document.querySelector("#a");
const b = document.querySelector("#b");
// в a — через innerHTML, в b — через textContent
console.log(a.children.length);
console.log(b.children.length);
console.log(b.textContent);
Предпросмотр
Нажмите «Запустить», чтобы увидеть результат.
Ожидаемый результат
1
0
Ali <b>Pro</b>
Подсказка 1
Каждый блок — одна строка присваивания: a.innerHTML = userName;
Подсказка 2
Второй блок — точно так же, но свойство textContent. В #a появится настоящий тег <b> (1 ребёнок), в #b — просто текст с видимыми угловыми скобками (0 детей).
ЗАДАЧА 3.3
Меню через map и join
средняя
Соберите HTML-строку из массива dishes: каждое блюдо — <li>. Используйте map и join(""). Запишите строку в #menu через innerHTML и выведите в консоль количество детей и саму разметку.
Меню из массива · Ctrl + Enter
<ulid="menu"></ul>
const dishes = ["Самбуса", "Курутоб", "Шакароб"];
const menu = document.querySelector("#menu");
// соберите строку и положите в menu
console.log(menu.children.length);
console.log(menu.innerHTML);
Внешний вид элемента почти всегда задаётся в CSS. JavaScript не должен красить страницу сам, его работа — включать и выключать готовые классы. Вы пишете в CSS, как выглядит .hot, а в JS только решаете, когда элементу его выдать. Так код остаётся чистым, а дизайн меняется в одном месте.
Аналогия из жизни
Класс — это форма на курсе: бейдж «преподаватель», «студент», «гость». Бейдж один раз придумали, дизайн утвердили. Охранник (JS) не рисует каждому новый бейдж, а просто выдаёт готовый или забирает. Выдать — add, забрать — remove, поменять на противоположный — toggle.
classList: управляем классами
Метод
Что делает
el.classList.add("hot")
добавляет класс (если уже есть — ничего не происходит)
const items = document.querySelectorAll(".item");
items[1].classList.add("hot"); // выдали бейдж «hot» Курутобу
console.log(items[1].className); // item hot
console.log(items[1].classList.contains("hot")); // true
console.log(items[0].classList.contains("hot")); // false
items[1].classList.toggle("hot"); // был — стал выключен
console.log(items[1].className); // item
Предпросмотр
Нажмите «Запустить», чтобы увидеть результат.
Частая ошибка: className = "…" затирает все классы
Свойство className хранит всю строку классов. Если записать в него новое значение, все прежние классы пропадут. Для добавления и удаления используйте classList.
Можно редактировать · Ctrl + Enter
const el = document.createElement("div");
el.className = "card sale";
el.className = "hot"; // затёрли «card» и «sale»!
console.log(el.className); // → hot
el.className = "card sale";
el.classList.add("hot"); // правильно: добавили к существующим
console.log(el.className); // → card sale hot
style: быстрый, но грубый способ
Через el.style.свойство можно задать стиль прямо у элемента. Названия свойств пишутся в camelCase: background-color становится backgroundColor, а значения — всегда строки:
Правило: если стиль нужен надолго или нескольким элементам — заведите класс. style оставляйте для значений, которые рассчитываются в коде (ширина полоски загрузки, позиция). Так JS не смешивается с дизайном, и ИИ-помощнику проще вам помогать.
Атрибуты: getAttribute, setAttribute и свойства
Атрибуты — это то, что пишется в открывающем теге: href, src, alt, disabled, data-id. Читать и менять их можно двумя способами:
Хотите «привязать» к элементу собственные данные — например, id товара или его цену? Для этого придуманы атрибуты, начинающиеся с data-. В JS они доступны через el.dataset, причём имя после data- превращается в свойство:
const buy = document.querySelector("#buy");
console.log(buy.dataset.id); // "17" — всегда строка!
console.log(buy.dataset.price); // "35"
console.log(buy.dataset.productName); // data-product-name → productName
console.log(Number(buy.dataset.price) * 2); // 70 — для расчётов превращаем в число
buy.dataset.price = "40"; // запись тоже через dataset
console.log(buy.getAttribute("data-price")); // 40 — атрибут в разметке изменился
buy.disabled = true; // свойство disabled — кнопка серая и неактивна
console.log(buy.hasAttribute("disabled")); // true
Предпросмотр
Нажмите «Запустить», чтобы увидеть результат.
Ловушка: всё в dataset — строки
dataset.price вернёт "35", а не 35. Складывать такие значения опасно: "35" + "5" даст "355". Перед расчётами всегда превращайте в число через Number(...).
const items = document.querySelectorAll(".item");
// items[1] — «Курутоб»: добавить класс hot// items[2] — «Шакароб»: убрать класс oldfor (const item of items) {
console.log(item.className);
}
Предпросмотр
Нажмите «Запустить», чтобы увидеть результат.
Ожидаемый результат
item
item hot
item
Подсказка 1
Используйте classList.add("...") и classList.remove("..."), а не className =.
Подсказка 2
items[1].classList.add("hot"); и items[2].classList.remove("old");
ЗАДАЧА 4.2
Панель: toggle и contains
лёгкая
У панели изначально классы panel open. Выполните по порядку: 1) выведите, есть ли класс open; 2) переключите open и снова выведите, есть ли он; 3) переключите open обратно, добавьте переключением класс dark и выведите className.
Панель · Ctrl + Enter
<divid="panel"class="panel open">Меню</div>
const panel = document.querySelector("#panel");
// 1) вывести panel.classList.contains("open")// 2) toggle("open") и снова вывести contains// 3) ещё раз toggle("open"), toggle("dark") и вывести panel.className
Предпросмотр
Нажмите «Запустить», чтобы увидеть результат.
Ожидаемый результат
true
false
panel open dark
Подсказка 1
После первого toggle("open") класса не будет (false), после второго — он вернётся в конец списка классов.
Подсказка 2
Третий шаг: panel.classList.toggle("open"); panel.classList.toggle("dark"); console.log(panel.className); Порядок классов — по порядку добавления.
ЗАДАЧА 4.3
Ссылка и кнопка покупки
средняя
Выполните четыре действия и выведите результат каждого: 1) смените href ссылки на https://makefirststep.tj и выведите его через getAttribute; 2) выведите цену из data-price, умноженную на 2 (как число); 3) смените data-id на 18 через dataset и выведите значение через getAttribute("data-id"); 4) отключите кнопку и выведите hasAttribute("disabled").
const link = document.querySelector("#link");
const buy = document.querySelector("#buy");
// 1) setAttribute("href", ...) и вывести getAttribute("href")// 2) вывести Number(buy.dataset.price) * 2// 3) buy.dataset.id = "18" и вывести getAttribute("data-id")// 4) buy.disabled = true и вывести hasAttribute("disabled")
Предпросмотр
Нажмите «Запустить», чтобы увидеть результат.
Ожидаемый результат
https://makefirststep.tj
70
18
true
Подсказка 1
link.setAttribute("href", "https://makefirststep.tj"); — первым аргументом имя атрибута, вторым значение.
Подсказка 2
Свойство disabled у кнопки — логическое: buy.disabled = true;. После этого атрибут disabled появляется в разметке, и hasAttribute вернёт true.
ЗАДАЧА 4.4
Светофор цен
сложная
У каждой карточки есть data-price. Переберите карточки: если цена 50 и больше — добавьте класс expensive, если меньше 20 — класс sale, иначе ничего. Выведите className каждой карточки, а затем количество карточек с классом expensive (через querySelectorAll).
const cards = document.querySelectorAll(".card");
for (const card of cards) {
const price = Number(card.dataset.price);
// добавьте нужный класс
console.log(card.className);
}
// сколько карточек с классом expensive
Предпросмотр
Нажмите «Запустить», чтобы увидеть результат.
Ожидаемый результат
card expensive
card
card
card sale
1
Подсказка 1
Внутри цикла нужны два if: if (price >= 50) card.classList.add("expensive"); и такой же для price < 20. Не забудьте Number(...) — у вас она уже есть.
Подсказка 2
В конце: console.log(document.querySelectorAll(".expensive").length);
05 Создаём и удаляем элементы
Пока мы только находили и меняли то, что уже написано в HTML. Но у настоящего сайта, например интернет-магазина, список товаров приходит из данных, и разметки под него заранее никто не пишет. Элементы нужно создавать из кода. Это три шага:
Создали — document.createElement("li"). Элемент появился «на складе»: он существует, но на странице его пока не видно.
Вставили — parent.append(li). Только после этого он появляется на странице.
Аналогия из жизни
Бозор. Продавец достаёт товар из мешка (createElement), вешает ценник (настройка) и только потом выкладывает на прилавок (append). Пока товар в мешке, покупатели его не видят. Самая частая ошибка новичка — сделать два первых шага и забыть про прилавок.
const menu = document.querySelector("#menu");
const li = document.createElement("li"); // 1. создали
li.textContent = "Оши палав"; // 2. настроили
li.classList.add("item");
menu.append(li); // 3. вставили — теперь он на странице
console.log(menu.children.length); // 1
Предпросмотр
Нажмите «Запустить», чтобы увидеть результат.
Куда вставлять
Метод
Куда попадёт элемент
parent.append(el)
в конец родителя
parent.prepend(el)
в начало родителя
ref.before(el)
перед элементом ref, на одном уровне с ним
ref.after(el)
после элемента ref
el.remove()
удаляет элемент со страницы
el.cloneNode(true)
создаёт копию (с содержимым, если true)
parent.replaceChildren()
удаляет всех детей. С аргументами — заменяет детей на переданные
append умеет принимать несколько элементов сразу: card.append(title, price). Если передать строку, она вставится как текст (не как HTML), так что это безопасно.
const queue = document.querySelector("#queue");
functionperson(name) {
const li = document.createElement("li");
li.textContent = name;
return li;
}
queue.querySelector(".gone").remove(); // ушла Мадина
queue.prepend(person("Нилуфар")); // VIP — в начало
queue.append(person("Далер")); // обычный — в конец
queue.children[1].after(person("Сабрина")); // после Рустама (он теперь второй)
console.log(Array.from(queue.children, li => li.textContent).join(", "));
// Нилуфар, Рустам, Сабрина, Фарход, Далер
Предпросмотр
Нажмите «Запустить», чтобы увидеть результат.
Элемент не копируется, а переезжает
Один и тот же элемент не может быть в двух местах дерева. Если вставить уже существующий элемент в другое место, он переедет, а на старом месте исчезнет. Нужна копия — делайте cloneNode(true).
Можно редактировать · Ctrl + Enter
const a = document.createElement("ul");
const b = document.createElement("ul");
const li = document.createElement("li");
li.textContent = "Самбуса";
a.append(li);
b.append(li); // переехал из a в b
console.log(a.children.length, b.children.length); // → 0 1
a.append(li.cloneNode(true)); // копия остаётся и там, и там
console.log(a.children.length, b.children.length); // → 1 1
createElement или innerHTML?
createElement + append
innerHTML со строкой
Записывается
длиннее
короче, похоже на настоящий HTML
Безопасность
текст всегда через textContent — безопасно
опасно, если в строке чужой текст
Старые ссылки на элементы
сохраняются
ломаются при += и перезаписи
Когда выбирать
данные от пользователя и API, когда элемент нужен в переменной
статичный шаблон, который вы написали сами
В этом уроке мы идём по безопасному пути: createElement. Он чуть длиннее, но учит видеть структуру страницы как дерево — а это именно то, что потом пригодится в React.
Частая ошибка: забыли вставить
Создать элемент — не значит показать его. Без append (или prepend, before, after) он остаётся «в мешке». Если на странице ничего не появилось, а ошибок нет, — первым делом проверьте, что вставка действительно вызывается.
Создайте <li> с текстом Самбуса и вставьте его в список #menu. Выведите количество детей списка и его текст.
Пустое меню · Ctrl + Enter
<ulid="menu"></ul>
const menu = document.querySelector("#menu");
// создайте li, задайте текст, вставьте в menu
console.log(menu.children.length);
console.log(menu.textContent);
Предпросмотр
Нажмите «Запустить», чтобы увидеть результат.
Ожидаемый результат
1
Самбуса
Подсказка 1
Три шага: createElement("li"), затем textContent, затем menu.append(...).
Подсказка 2
const li = document.createElement("li"); li.textContent = "Самбуса"; menu.append(li);
ЗАДАЧА 5.2
Три пункта циклом
лёгкая
Для каждого блюда из массива создайте <li> с классом item и вставьте в #menu. Выведите количество пунктов, текст последнего и класс первого.
Меню циклом · Ctrl + Enter
<ulid="menu"></ul>
const dishes = ["Нон", "Сомса", "Чой"];
const menu = document.querySelector("#menu");
// цикл по dishes: создать li, текст, класс item, вставить
console.log(menu.children.length);
console.log(menu.lastElementChild.textContent);
console.log(menu.firstElementChild.className);
Предпросмотр
Нажмите «Запустить», чтобы увидеть результат.
Ожидаемый результат
3
Чой
item
Подсказка 1
Тело цикла for (const dish of dishes) { ... } содержит те же три шага, что и в задаче 5.1, плюс li.classList.add("item").
Подсказка 2
Создание нового li должно быть внутри цикла — на каждый пункт свой элемент.
ЗАДАЧА 5.3
Очередь в банке
средняя
Выполните четыре операции с очередью: 1) удалите того, у кого класс old; 2) поставьте Зарина в самое начало; 3) поставьте Далер сразу после Алии; 4) добавьте Нилуфар в конец. Вспомогательная функция make уже есть.
Сначала предскажите два числа в первых двух строках вывода — сколько элементов будет в левом и правом списках после каждой операции. Потом запустите. Затем допишите код: клон перед вставкой должен получить текст Курутоб (копия) (к тексту клона допишите " (копия)"), а в конце выведите текст последнего элемента правого списка.
const left = document.querySelector("#left");
const right = document.querySelector("#right");
right.append(left.firstElementChild); // переезд
console.log(left.children.length, right.children.length);
const clone = left.firstElementChild.cloneNode(true);
// допишите к тексту клона " (копия)"
right.append(clone);
console.log(left.children.length, right.children.length);
// выведите текст последнего элемента правого списка
Предпросмотр
Нажмите «Запустить», чтобы увидеть результат.
Ожидаемый результат
2 1
2 2
Курутоб (копия)
Подсказка 1
Первый appendпереносит «Самбусу» — слева остаётся 2, справа 1. Второй вставляет копию — слева по-прежнему 2, справа 2.
Подсказка 2
clone.textContent += " (копия)"; до вставки, а в конце — console.log(right.lastElementChild.textContent);
06 Рендер: из массива в карточки
Теперь соединим всё, что вы знаете. В уроках 6 и 7 вы работали с массивами объектов — каталогами товаров, студентов, заказов. Теперь эти данные можно нарисовать. Слово рендер (render) как раз и означает «из данных построить то, что видно на экране». Схема всегда одна и та же:
Данные — массив объектов: const products = [{ title: "Нон", price: 4 }, …]. Это главный источник правды.
Функция-фабрикаcreateCard(product) — получает один объект и возвращает готовый элемент.
Функция рендераrender(items) — очищает контейнер и вставляет в него карточки для всего массива.
Оши палав на свадьбе. Рецепт и список гостей — это данные. Повар — функция-фабрика: на входе рис и мясо, на выходе одна порция. А официант — render: проходит по всему списку гостей, ставит порцию перед каждым. Новый гость пришёл — не нужно менять рецепт, просто ещё одна порция по тому же рецепту.
Запустите, а затем нажмите кнопку «Очистить» в preview — карточки исчезнут. Измените цену или добавьте в массив ещё товар, запустите снова: страница перерисуется сама. Меняем данные — страница следует за ними.
Разбираем render по частям
items.map(createCard)
map передаёт каждый товар в createCard и собирает возвращённые элементы в новый массив.
...cards
Оператор spread из прошлого урока: «высыпает» массив элементов в аргументы replaceChildren(card1, card2, …).
replaceChildren(…)
Сначала убирает всех старых детей, потом вставляет новых. Поэтому повторный render не дублирует карточки.
replaceChildren()
Без аргументов — просто очищает контейнер. Это и есть «кнопка очистки».
Кнопка пока на «честном слове»
Строка clearBtn.onclick = … — первый взгляд на события. Пока запомните её как «когда нажмут на кнопку — сделай это». Полностью события (addEventListener, объект события, делегирование) мы разберём в следующем уроке. Для домашнего задания этой строки достаточно.
Пустое состояние
Что видит пользователь, если по его фильтру ничего не нашлось? Пустую белую страницу — как будто сайт сломался. Хороший рендер всегда обрабатывает пустой массив:
render с пустым состоянием
functionrender(items) {
if (items.length ===0) {
const empty = document.createElement("p");
empty.className = "empty";
empty.textContent = "Ничего не найдено";
catalog.replaceChildren(empty);
return; // ранний выход — как в уроке про функции
}
catalog.replaceChildren(...items.map(createCard));
}
Две частые ошибки рендера
1. Вставлять карточки, не очистив контейнер: каждый новый render будет дописывать товары к старым, и список будет расти. Используйте replaceChildren.
2. Создавать элемент вне функции или вне цикла: const card = document.createElement(…) один раз и затем много раз append(card). Элемент один — значит, он будет переезжать, и в итоге на странице окажется одна карточка (помните раздел 5).
Тот же рендер строкой
Короткий вариант с шаблонной строкой тоже допустим, если данные ваши и безопасные. Сравните — результат одинаковый:
Вариант со строкой (только для своих, безопасных данных)
Напишите функцию render(items): она очищает #catalog; если массив пуст — вставляет абзац <p class="empty">Ничего не найдено</p>; иначе — по одному <div class="card"> с названием товара. Проверьте на трёх наборах данных.
Начните функцию с проверки if (items.length === 0) и ранним return. Остальной код будет только для непустого массива.
Подсказка 2
В обеих ветках используйте catalog.replaceChildren(...): в пустой — с одним абзацем, в обычной — с массивом карточек через items.map.
ЗАДАЧА 6.3
Карточка с отметкой «нет в наличии»
средняя
Допишите createCard({ title, price, inStock }): внутри <article class="card"> лежат <h3> с названием и <p class="price"> с текстом 60 сомони. Если товара нет в наличии, карточке нужен ещё класс out. Деструктуризация в параметрах — как на уроке про объекты.
Схема та же, что в примере выше: card, h3, p.price, затем card.append(h3, p) и return card.
Подсказка 2
if (!inStock) card.classList.add("out"); — до return.
ЗАДАЧА 6.4
Группировка по категориям
сложная
Покажите товары по разделам: для каждой категории создайте <section> с заголовком <h3> (название категории) и списком <ul>, где каждый товар — <li>. Сначала сгруппируйте массив через reduce в объект «категория → массив названий», потом постройте DOM из Object.entries(groups). Порядок разделов — порядок первого появления категории.
Каталог по разделам · Ctrl + Enter
<divid="catalog"></div>
const products = [
{ title: "Нон", category: "Продукты" },
{ title: "Зардолу", category: "Продукты" },
{ title: "Пиала", category: "Кухня" },
{ title: "Чапан", category: "Сувениры" },
{ title: "Тюбетейка", category: "Сувениры" }
];
const catalog = document.querySelector("#catalog");
// 1) groups = { Продукты: ["Нон", "Зардолу"], Кухня: ["Пиала"], ... } через reduce// 2) для каждой пары [category, titles] создать section > h3 + ul > li// 3) вставить все section в catalogconst sections = catalog.querySelectorAll("section");
console.log(sections.length);
for (const section of sections) {
console.log(section.querySelector("h3").textContent + ": " + section.querySelectorAll("li").length);
}
Предпросмотр
Нажмите «Запустить», чтобы увидеть результат.
Ожидаемый результат
3
Продукты: 2
Кухня: 1
Сувениры: 2
Подсказка 1
Группировка: products.reduce((acc, p) => { if (!acc[p.category]) acc[p.category] = []; acc[p.category].push(p.title); return acc; }, {}). Начальное значение — пустой объект.
Подсказка 2
Цикл for (const [category, titles] of Object.entries(groups)). Внутри создайте section, h3 (текст — category), ul; для каждого названия из titles — li. Собранный section — catalog.append(section).
07 Разделение труда: ИИ пишет вёрстку, вы — логику
DOM — идеальное место, чтобы научиться работать с ИИ правильно. Разметка и CSS — это объёмная, но однообразная работа: ИИ делает её быстро и обычно хорошо. А вот логика — какие данные, в каком порядке, что сделать при пустом списке — это то, что вы должны понимать до последней строки. Поэтому в нашем курсе правило такое:
Правило урока 8
ИИ пишет HTML и CSS. JavaScript-логику вы пишете сами. Сначала 15 минут пробуете сами, и только потом идёте с промптом к ИИ. Не вставляйте код, который не можете объяснить строчка за строчкой.
Отдаём ИИ
Делаем сами
Разметка карточки, формы, шапки
Структура данных (какие поля у товара)
CSS: сетка, цвета, отступы, адаптивность
Функции createCard и render
Тестовые данные (массив из 10 товаров)
Проверка: данные правдоподобны, типы верные
Объяснение незнакомого метода
Решение, какой метод и когда применить
Мини-справка: BEM — имена классов без хаоса
Когда ИИ делает вёрстку, просите именовать классы по BEM. Это договорённость об именах, чтобы по классу сразу было видно, что это такое:
Блок
Самостоятельный компонент: product-card.
Элемент
Часть блока, через два подчёркивания: product-card__title, product-card__price.
Модификатор
Вариант блока, через два дефиса: product-card--out («нет в наличии»). В JS его включают и выключают через classList — тот самый разговор про бейджи из раздела 4.
Промпт на HTML-шаблон
Сравните два запроса к ИИ. Первый — как делают новички. Второй — по формуле курса: контекст → задача → ограничения → формат ответа → проверка.
сделай карточку товара
Контекст: я учу JavaScript, делаю учебный интернет-магазин на чистом HTML/CSS/JS без библиотек. Мне нужна только вёрстка — JavaScript я напишу сам.
Задача: сделай HTML-шаблон одной карточки товара (название, цена, кнопка «В корзину») и CSS к нему, плюс CSS для адаптивной сетки таких карточек (grid).
Ограничения: классы по BEM (product-card, product-card__title, product-card__price, product-card__btn, модификатор product-card--out для товара, которого нет в наличии). Без JavaScript, без inline-стилей, без onclick, без внешних картинок и шрифтов. Светлая тема, скруглённые углы.
Формат ответа: два блока кода — HTML и CSS. После них — список классов и что каждый значит, в три-пять строк.
Проверка: напиши, какие элементы шаблона мне придётся заполнять из JavaScript.
Что получает ИИ из второго промпта? Стек, уровень, границы («JavaScript не пиши»), конкретные имена классов, формат ответа и просьбу самому проверить себя. А вам остаётся логика — и вы точно знаете, на какие классы она опирается.
Что ИИ мог ответить — и как это превратить в код
Допустим, ИИ вернул такую разметку одной карточки:
Ваша задача — не копировать эту карточку десять раз, а описать её один раз в функции. Каждый тег шаблона превращается в три строки: создали, настроили, вставили.
Есть ли в ответе <script>, onclick="…", inline-стили style="…"? Вы просили без них — значит, убираем.
Совпадают ли имена классов с теми, что вы будете использовать в JS? Одна лишняя буква — и querySelector вернёт null.
Подходящие ли теги: article для карточки, h3 для названия, button для кнопки (а не div с кликом)?
Нет ли внешних картинок и шрифтов, которые не загрузятся?
Если ИИ предложил innerHTML со вставкой данных — спросите, безопасно ли это, и перепишите на textContent.
Не придумал ли ИИ несуществующих методов? Проверьте незнакомое в MDN.
Не просите «напиши весь рендер»
Если попросить ИИ написать createCard и render, он напишет рабочий код за пять секунд — и вы ничему не научитесь. На проверке ДЗ преподаватель может попросить объяснить любую строку. Хороший способ использовать ИИ для логики: сначала написать свой вариант, а потом попросить «объясни построчно, что делает мой код, и назови 2 места, где он сломается».
Вот моя функция createCard: [код]. Объясни построчно, что она делает. Ничего не переписывай. В конце назови два крайних случая, на которых она сломается (например, нет поля price).
Практика с ИИ
Одна задача на проверку чужого кода, одна — на написание своего промпта.
ЗАДАЧА 7.1
Проверьте код от ИИ
средняя
Вы попросили ИИ нарисовать каталог. Он вернул код ниже. В нём три ошибки. Требования: на странице должно быть две карточки; в каждой — заголовок с названием и абзац .price с текстом вида 450 сомони. Найдите и исправьте ошибки по одной, перезапуская код. Сначала прочитайте ошибку в консоли.
Первая ошибка видна сразу: в консоли null. Сравните селектор с классом контейнера в HTML. Вторая: вспомните, что происходит, когда один и тот же элемент вставляют несколько раз. Третья — в тексте цены: чего не хватает по требованиям?
Подсказка 2
Селектор должен быть ".catalog". Создание card нужно перенести внутрьforEach, чтобы на каждый товар был свой элемент. В шаблонной строке цены допишите сомони после ${p.price}.
ЗАДАЧА 7.2
Составьте свой промпт
лёгкая
Составьте по формуле курса промпт для ИИ: HTML и CSS карточки курса MakeFirstStep (название, длительность, цена, кнопка «Записаться»). Обязательно: BEM, без JavaScript, без inline-стилей, ответ — два блока кода. Отправьте его ИИ, проверьте ответ по чек-листу выше и сохраните промпт в файл prompt.txt — он пригодится для домашнего задания.
Для этой задачи нет автопроверки: отметьте её выполненной, когда промпт составлен, а ответ проверен по всем пунктам чек-листа.
Подсказка 1
Пройдите по формуле: контекст (проект, уровень), задача (одна), ограничения (BEM, без JS), формат (два блока кода), проверка (что заполнять из JS).
Подсказка 2
Возьмите промпт из этого раздела и замените товар на курс: поля title, duration, price, кнопка «Записаться».
08 Практика на уроке: собираем каталог карточек
Время собрать всё вместе. Цель: страница магазина, на которой 10 карточек рисуются из массива, а кнопка «Очистить» убирает их все. Вёрстку и стили мы получили от ИИ (они уже лежат в песочницах ниже), логику пишете вы — шаг за шагом.
Данные. Заведите массив из 10 объектов: title, price, category, inStock, rating. В реальной жизни такие данные можно попросить у ИИ (урок 7) — но проверить 2–3 объекта на глаз всё равно нужно.
Фабрика карточки. Функция createCard(product): создаёт article, h3, p, настраивает классы по BEM и текст через textContent, собирает через append и возвращает карточку. Для товара не в наличии — модификатор product-card--out.
Рендер. Функция render(items): catalog.replaceChildren(...items.map(createCard)).
Вызов.render(products) — и посмотрите на страницу.
Проверка: вывод в консоль количества карточек — до очистки, после и после повторного рендера.
Как идти по шагам
После каждого шага нажимайте ▶ Запустить и смотрите на preview. Не пишите всё сразу: если на шаге 3 карточка не появилась, вы точно знаете, где искать. Это тот же приём, что и в «Разделении труда» раздела 7: маленькие шаги, проверка после каждого.
Практика: магазин «Бозор»
Две задачи: сначала страница целиком, потом улучшения. В песочнице уже есть вёрстка и CSS от ИИ; данные тоже готовы.
ЗАДАЧА 8.1
Каталог из 10 карточек и кнопка очистки
средняя
Допишите createCard, render и кнопку очистки по шагам выше. Проверочный код внизу сам «нажмёт» кнопку методом click(). Он выведет: сколько карточек стало, сколько из них «нет в наличии», сколько после очистки и сколько после повторного рендера.
Каталог уже работает. Улучшите его: 1) товарам с рейтингом 4.8 и выше добавьте класс product-card--hit и значок <span class="product-card__badge">Хит</span> внутрь карточки; 2) после каждого render показывайте в #stats строку вида Хитов: 3 из 10 (общее число — это items.length, хиты считайте по массиву items). Значок вставляйте только через createElement.
Условие product.rating >= 4.8 внутри createCard: добавьте класс через classList, создайте span, задайте класс и текст и вставьте его в карточку через card.append(...).
Подсказка 2
Счётчик: const hits = items.filter(p => p.rating >= 4.8).length; и stats.textContent = `Хитов: ${hits} из ${items.length}`;
09 Домашнее задание
Тема: «Страница, собранная из JS». Соберите свою страницу-каталог: весь контент появляется из массива, а в HTML-файле нет ни одной карточки.
Что нужно сделать
☐ Данные. Массив минимум из 10 объектов на тему на ваш выбор: магазин, меню чайханы, курсы MakeFirstStep, маршрутки Душанбе, фильмы. У каждого объекта минимум 4 поля, среди которых есть title и логическое поле (например, inStock).
☐ Вёрстка от ИИ. HTML-шаблон и CSS получены по промпту по формуле курса. Промпт сохранён в файле prompt.txt. Классы — по BEM.
☐ createCard(item). Отдельная функция, которая через createElement собирает карточку и возвращает её. Весь текст — через textContent.
☐ render(items). Отдельная функция: очищает контейнер через replaceChildren и вставляет карточки. Для пустого массива показывает «Ничего не найдено».
☐ Кнопка очистки. Кнопка «Очистить» убирает все карточки (через onclick, как в уроке). Бонус: кнопка «Показать всё» возвращает их.
☐ Счётчик. Текст «Показано: N» обновляется внутри render.
☐ Модификатор. Карточки, для которых логическое поле ложно, получают дополнительный класс (--out, --sold и т. п.) через classList, и CSS их выделяет.
☐ Бонус. Вызовите render с отфильтрованным массивом (например, только дешёвые) — просто строкой в коде, без кнопок. Кнопки-фильтры будут на следующем уроке.
Правила работы с ИИ в этом ДЗ
ИИ можно использовать для вёрстки, CSS и тестовых данных. Логику (createCard, render, очистку, счётчик) пишете сами. На проверке преподаватель может попросить объяснить любую строку вашего JS. Если не можете — оценка снижается.
Что сдавать
Три файла: index.html, style.css, script.js и файл prompt.txt с промптом, по которому ИИ сделал вёрстку.
Скриншот страницы с 10 карточками и скриншот после нажатия «Очистить».
Два-три предложения в чате группы: что оказалось самым сложным и что вы исправили после проверки кода ИИ.
Как оценим (0–10)
Критерий
Баллы
Не менее 10 карточек рисуются из массива, в HTML их нет
3
createCard и render — отдельные функции, render вызывается повторно без дублей
1
Кнопка очистки работает
1
Счётчик и пустое состояние
1
Модификатор класса через classList и стиль для него
1
Безопасная вставка: данные через textContent, а не innerHTML
1
Чистый код: осмысленные имена, нет копипасты, найденные элементы в переменных
1
Промпт по формуле в prompt.txt и умение объяснить код
1
Совет, с чего начать
Сначала вручную напишите в HTML одну карточку и добейтесь, что она красивая. Потом удалите её из HTML и воспроизведите в createCard — так вы точно знаете, какая структура нужна. Используйте console.log после каждого шага и вкладку Elements в DevTools, чтобы увидеть, что реально построил ваш код.
✓ Шпаргалка урока
DOM — живое дерево объектов, которое браузер строит из HTML. JavaScript работает с ним через document. Файл HTML при этом не меняется.
Навигация:parentElement, children, firstElementChild, lastElementChild, nextElementSibling, previousElementSibling. childNodes считает ещё и пробелы.
Поиск:querySelector(sel) — первый элемент или null; querySelectorAll(sel) — NodeList (для map — Array.from). Искать можно внутри элемента: card.querySelector(".price").
Текст и разметка:textContent — безопасный текст; innerHTML — разметка, только для своих данных. Чужой текст — всегда textContent. += для innerHTML ломает старые ссылки.
Классы:classList.add / remove / toggle / contains. className = затирает всё. Дизайн — в CSS-классах, а не в el.style.
Атрибуты:getAttribute / setAttribute / removeAttribute / hasAttribute; свойства value, disabled, hidden; свои данные — data-* и el.dataset.name (всегда строка).
Создание:createElement → настроить → append / prepend / before / after. Удаление — el.remove(), очистка — parent.replaceChildren(), копия — cloneNode(true). Элемент не копируется, а переезжает.
Рендер: данные (массив) → createCard(item) возвращает элемент → render(items) делает replaceChildren(...items.map(createCard)). Всегда обрабатывайте пустой массив.
С ИИ: ИИ пишет HTML и CSS (BEM, без JS и inline-стилей), вы — логику. Проверяйте имена классов, безопасность вставки и выдуманные методы. Не вставляйте код, который не можете объяснить.