Ты открываешь Notion и создаёшь новый блок текста. Страница не перезагружается — текст появляется мгновенно. Как это работает? JavaScript изменяет страницу прямо в браузере, не обращаясь к серверу.
DOM (Document Object Model) — это JavaScript-представление HTML-страницы в виде дерева объектов. Каждый HTML-тег становится узлом дерева, которым можно управлять из JS.
// Один элемент — первое совпадение по CSS-селектору
const btn = document.querySelector('.submit-btn')
const form = document.querySelector('#login-form')
const h1 = document.querySelector('h1')
// Все совпадения — NodeList (forEach есть, map — нет!)
const cards = document.querySelectorAll('.product-card')
cards.forEach(card => card.classList.add('loaded'))
// Для методов массива — конвертируй:
const prices = Array.from(document.querySelectorAll('.price'))
.map(el => parseFloat(el.textContent))const el = document.querySelector('.product-title')
// textContent — безопасно, обрабатывает как текст
el.textContent = 'MacBook Pro 16"'
// innerHTML — опасно с пользовательскими данными! XSS-уязвимость
el.innerHTML = '<b>MacBook Pro</b>' // можно, если данные не от пользователя
el.innerHTML = '<b>' + userName + '</b>' // НЕЛЬЗЯ — XSS!
// value — для полей ввода
const input = document.querySelector('#search')
console.log(input.value) // текущее значение поля
input.value = '' // очистить полеconst link = document.querySelector('a')
link.getAttribute('href') // '/products/42'
link.setAttribute('href', '/cart')
link.removeAttribute('disabled')
// dataset — удобный доступ к data-* атрибутам
// HTML: <button data-product-id="42" data-action="add-to-cart">
const btn = document.querySelector('button')
console.log(btn.dataset.productId) // '42' (snake-case → camelCase автоматически)
console.log(btn.dataset.action) // 'add-to-cart'
btn.dataset.productId = '99' // устанавливает data-product-id="99"const card = document.querySelector('.product-card')
card.classList.add('in-cart') // добавить класс
card.classList.remove('loading') // удалить класс
card.classList.toggle('selected') // добавить если нет, убрать если есть
card.classList.contains('active') // true/false
// toggle с принудительным значением
card.classList.toggle('sale', product.onSale) // true = добавить, false = убратьfunction createProductCard(product) {
const card = document.createElement('div')
card.className = 'product-card'
card.dataset.id = product.id
const title = document.createElement('h3')
title.textContent = product.name // безопасно — не парсит HTML
const price = document.createElement('p')
price.textContent = product.price.toLocaleString('ru-RU') + ' ₽'
card.append(title, price) // добавляет несколько узлов сразу
return card
}
const container = document.querySelector('#products')
container.appendChild(createProductCard({ id: 1, name: 'Ноутбук', price: 80000 }))1. innerHTML с пользовательскими данными — XSS:
// Сломано — XSS уязвимость:
const comment = getUserInput() // '<script>stealCookies()</script>'
el.innerHTML = 'Комментарий: ' + comment // выполнит скрипт!
// Исправлено:
el.textContent = 'Комментарий: ' + comment // отобразит как текст2. querySelector вернул null — не проверили:
// Сломано — TypeError если элемента нет на странице:
const btn = document.querySelector('#nonexistent')
btn.addEventListener('click', handler) // Cannot read properties of null
// Исправлено — optional chaining:
const btn = document.querySelector('#submit-btn')
btn?.addEventListener('click', handler)3. Затёрли все классы вместо добавления одного:
// Сломано — затирает существующие классы:
el.className = 'active' // было 'card card--large' — всё потеряно!
// Исправлено:
el.classList.add('active') // добавляет к существующим<my-dropdown>) с Shadow DOMВ этом курсе document недоступен — используем объекты-симуляторы с теми же методами.
Симуляция DOM: создание карточек товаров интернет-магазина
// Симуляция DOM API для sandbox (в браузере — то же самое, но нативно)
function makeElement(tag) {
return {
tag,
textContent: '',
className: '',
dataset: {},
children: [],
classList: {
_cls: new Set(),
add(...names) { names.forEach(n => this._cls.add(n)) },
remove(n) { this._cls.delete(n) },
toggle(n, force) {
const has = this._cls.has(n)
if (force !== undefined) { force ? this._cls.add(n) : this._cls.delete(n) }
else { has ? this._cls.delete(n) : this._cls.add(n) }
},
contains(n) { return this._cls.has(n) },
toString() { return [...this._cls].join(' ') },
},
append(...nodes) { nodes.forEach(n => this.children.push(n)) },
appendChild(child) { this.children.push(child); return child },
}
}
const document = { createElement: makeElement }
// Функция создания карточки — идентична браузерному коду
function createProductCard(product) {
const card = document.createElement('div')
card.className = 'product-card'
card.dataset.id = product.id
const title = document.createElement('h3')
title.className = 'product-card__title'
title.textContent = product.name // textContent — безопасно
const price = document.createElement('p')
price.className = 'product-card__price'
price.textContent = product.price.toLocaleString('ru-RU') + ' ₽'
const badge = document.createElement('span')
badge.classList.add('badge')
badge.classList.toggle('badge--sale', product.onSale)
badge.textContent = product.onSale ? 'Скидка' : 'Новинка'
const btn = document.createElement('button')
btn.className = 'btn btn--primary'
btn.dataset.productId = product.id
btn.dataset.action = 'add-to-cart'
btn.textContent = 'В корзину'
card.append(title, price, badge, btn)
return card
}
const products = [
{ id: 1, name: 'MacBook Pro 14"', price: 189990, onSale: true },
{ id: 2, name: 'AirPods Pro', price: 24990, onSale: false },
{ id: 3, name: 'Magic Mouse', price: 8990, onSale: true },
]
const container = document.createElement('div')
products.forEach(p => container.appendChild(createProductCard(p)))
// Читаем структуру как HTML
container.children.forEach(card => {
const [t, p, b, btn] = card.children
console.log(`<div class="${card.className}" data-id="${card.dataset.id}">`)
console.log(` <h3 class="${t.className}">${t.textContent}</h3>`)
console.log(` <p class="${p.className}">${p.textContent}</p>`)
console.log(` <span class="${b.classList}">${b.textContent}</span>`)
console.log(` <button data-action="${btn.dataset.action}">${btn.textContent}</button>`)
console.log('</div>')
})Ты открываешь Notion и создаёшь новый блок текста. Страница не перезагружается — текст появляется мгновенно. Как это работает? JavaScript изменяет страницу прямо в браузере, не обращаясь к серверу.
DOM (Document Object Model) — это JavaScript-представление HTML-страницы в виде дерева объектов. Каждый HTML-тег становится узлом дерева, которым можно управлять из JS.
// Один элемент — первое совпадение по CSS-селектору
const btn = document.querySelector('.submit-btn')
const form = document.querySelector('#login-form')
const h1 = document.querySelector('h1')
// Все совпадения — NodeList (forEach есть, map — нет!)
const cards = document.querySelectorAll('.product-card')
cards.forEach(card => card.classList.add('loaded'))
// Для методов массива — конвертируй:
const prices = Array.from(document.querySelectorAll('.price'))
.map(el => parseFloat(el.textContent))const el = document.querySelector('.product-title')
// textContent — безопасно, обрабатывает как текст
el.textContent = 'MacBook Pro 16"'
// innerHTML — опасно с пользовательскими данными! XSS-уязвимость
el.innerHTML = '<b>MacBook Pro</b>' // можно, если данные не от пользователя
el.innerHTML = '<b>' + userName + '</b>' // НЕЛЬЗЯ — XSS!
// value — для полей ввода
const input = document.querySelector('#search')
console.log(input.value) // текущее значение поля
input.value = '' // очистить полеconst link = document.querySelector('a')
link.getAttribute('href') // '/products/42'
link.setAttribute('href', '/cart')
link.removeAttribute('disabled')
// dataset — удобный доступ к data-* атрибутам
// HTML: <button data-product-id="42" data-action="add-to-cart">
const btn = document.querySelector('button')
console.log(btn.dataset.productId) // '42' (snake-case → camelCase автоматически)
console.log(btn.dataset.action) // 'add-to-cart'
btn.dataset.productId = '99' // устанавливает data-product-id="99"const card = document.querySelector('.product-card')
card.classList.add('in-cart') // добавить класс
card.classList.remove('loading') // удалить класс
card.classList.toggle('selected') // добавить если нет, убрать если есть
card.classList.contains('active') // true/false
// toggle с принудительным значением
card.classList.toggle('sale', product.onSale) // true = добавить, false = убратьfunction createProductCard(product) {
const card = document.createElement('div')
card.className = 'product-card'
card.dataset.id = product.id
const title = document.createElement('h3')
title.textContent = product.name // безопасно — не парсит HTML
const price = document.createElement('p')
price.textContent = product.price.toLocaleString('ru-RU') + ' ₽'
card.append(title, price) // добавляет несколько узлов сразу
return card
}
const container = document.querySelector('#products')
container.appendChild(createProductCard({ id: 1, name: 'Ноутбук', price: 80000 }))1. innerHTML с пользовательскими данными — XSS:
// Сломано — XSS уязвимость:
const comment = getUserInput() // '<script>stealCookies()</script>'
el.innerHTML = 'Комментарий: ' + comment // выполнит скрипт!
// Исправлено:
el.textContent = 'Комментарий: ' + comment // отобразит как текст2. querySelector вернул null — не проверили:
// Сломано — TypeError если элемента нет на странице:
const btn = document.querySelector('#nonexistent')
btn.addEventListener('click', handler) // Cannot read properties of null
// Исправлено — optional chaining:
const btn = document.querySelector('#submit-btn')
btn?.addEventListener('click', handler)3. Затёрли все классы вместо добавления одного:
// Сломано — затирает существующие классы:
el.className = 'active' // было 'card card--large' — всё потеряно!
// Исправлено:
el.classList.add('active') // добавляет к существующим<my-dropdown>) с Shadow DOMВ этом курсе document недоступен — используем объекты-симуляторы с теми же методами.
Симуляция DOM: создание карточек товаров интернет-магазина
// Симуляция DOM API для sandbox (в браузере — то же самое, но нативно)
function makeElement(tag) {
return {
tag,
textContent: '',
className: '',
dataset: {},
children: [],
classList: {
_cls: new Set(),
add(...names) { names.forEach(n => this._cls.add(n)) },
remove(n) { this._cls.delete(n) },
toggle(n, force) {
const has = this._cls.has(n)
if (force !== undefined) { force ? this._cls.add(n) : this._cls.delete(n) }
else { has ? this._cls.delete(n) : this._cls.add(n) }
},
contains(n) { return this._cls.has(n) },
toString() { return [...this._cls].join(' ') },
},
append(...nodes) { nodes.forEach(n => this.children.push(n)) },
appendChild(child) { this.children.push(child); return child },
}
}
const document = { createElement: makeElement }
// Функция создания карточки — идентична браузерному коду
function createProductCard(product) {
const card = document.createElement('div')
card.className = 'product-card'
card.dataset.id = product.id
const title = document.createElement('h3')
title.className = 'product-card__title'
title.textContent = product.name // textContent — безопасно
const price = document.createElement('p')
price.className = 'product-card__price'
price.textContent = product.price.toLocaleString('ru-RU') + ' ₽'
const badge = document.createElement('span')
badge.classList.add('badge')
badge.classList.toggle('badge--sale', product.onSale)
badge.textContent = product.onSale ? 'Скидка' : 'Новинка'
const btn = document.createElement('button')
btn.className = 'btn btn--primary'
btn.dataset.productId = product.id
btn.dataset.action = 'add-to-cart'
btn.textContent = 'В корзину'
card.append(title, price, badge, btn)
return card
}
const products = [
{ id: 1, name: 'MacBook Pro 14"', price: 189990, onSale: true },
{ id: 2, name: 'AirPods Pro', price: 24990, onSale: false },
{ id: 3, name: 'Magic Mouse', price: 8990, onSale: true },
]
const container = document.createElement('div')
products.forEach(p => container.appendChild(createProductCard(p)))
// Читаем структуру как HTML
container.children.forEach(card => {
const [t, p, b, btn] = card.children
console.log(`<div class="${card.className}" data-id="${card.dataset.id}">`)
console.log(` <h3 class="${t.className}">${t.textContent}</h3>`)
console.log(` <p class="${p.className}">${p.textContent}</p>`)
console.log(` <span class="${b.classList}">${b.textContent}</span>`)
console.log(` <button data-action="${btn.dataset.action}">${btn.textContent}</button>`)
console.log('</div>')
})Ты разрабатываешь страницу корзины для интернет-магазина. Используя симулятор DOM (уже подготовлен), напиши функцию `renderCart(items, container)`, которая для каждого товара создаёт `<li>` с: - классом `cart-item` - `data-id` равным `item.id` - textContent вида: `"Ноутбук — 80 000 ₽ x1"` После рендеринга выведи количество товаров и общую сумму заказа.
li.classList.add("cart-item"). li.dataset.id = item.id. li.textContent = item.name + " — " + item.price.toLocaleString("ru-RU") + " ₽ x" + item.qty. container.appendChild(li).