JavaScript: основы и практика разработки

Курс знакомит с базовым синтаксисом JavaScript, принципами работы языка и ключевыми возможностями для разработки веб-приложений. Вы изучите работу с DOM, событиями, асинхронностью и основы взаимодействия с сервером, закрепляя материал практикой.

1. Введение в JavaScript и базовый синтаксис

Введение в JavaScript и базовый синтаксис

JavaScript (JS) — язык программирования, который чаще всего используют для создания интерактивных веб-страниц, а также для разработки серверных приложений, скриптов автоматизации и инструментов.

В этой статье вы:

  • Поймёте, где работает JavaScript и как его запускать
  • Напишете первую программу
  • Разберёте базовый синтаксис: переменные, типы, операторы, условия, циклы, функции
  • Узнаете о самых частых ошибках новичков
  • Где работает JavaScript

    JavaScript исполняется в среде выполнения:

  • В браузере: код выполняется движком (например, V8 в Chrome, SpiderMonkey в Firefox)
  • На сервере: чаще всего в Node.js
  • Одна и та же языковая основа (стандарт ECMAScript) работает и там, и там, но окружение даёт разные возможности:

  • Браузер предоставляет API для работы со страницей (DOM), событиями, сетью
  • Node.js предоставляет API для работы с файловой системой, процессами, сетевыми сокетами
  • !Схема различий: язык один, окружения разные

    Полезные источники:

  • MDN: JavaScript Guide
  • MDN: JavaScript
  • Как запускать JavaScript

    Самый быстрый способ начать:

  • Откройте DevTools в браузере (обычно F12)
  • Перейдите на вкладку Console
  • Введите выражение и нажмите Enter
  • Пример:

    Если вы работаете в Node.js, можно запустить файл:

    Запуск в терминале:

    Первая программа: выражения, инструкции и точка с запятой

    В JavaScript код состоит из инструкций (statements). Часто инструкции заканчивают ;.

  • Во многих местах ; можно опускать, потому что работает механизм автоматической расстановки
  • На практике в учебных целях полезно ставить ;, чтобы избежать неожиданных ошибок
  • Пример нескольких инструкций:

    Комментарии

    Комментарии нужны, чтобы пояснять код. Они игнорируются при выполнении:

  • Однострочный комментарий начинается с //
  • Многострочный — между / и /
  • Переменные: let, const, var

    Переменная — это имя, связанное со значением.

    const

    const используют по умолчанию, если значение не нужно переназначать:

    Важно: const запрещает переназначение переменной, но не делает объект неизменяемым.

    let

    let используют, если значение будет меняться:

    var

    var — старый способ объявления. Сейчас обычно избегают из-за особенностей области видимости и поднятия (hoisting). В современном коде чаще всего выбирают const и let.

    Типы данных: что может хранить переменная

    JavaScript — язык с динамической типизацией: одна и та же переменная может хранить значения разных типов.

    Основные типы, которые будут постоянно встречаться:

  • number — числа (включая целые и дробные)
  • string — строки
  • booleantrue или false
  • null — явное "пустое" значение
  • undefined — значение "не задано"
  • object — объекты (включая массивы и функции как особые виды объектов)
  • Проверить тип можно оператором typeof:

    Отдельно важно знать про особенность:

    Строки и шаблонные литералы

    Строки можно складывать через +, но чаще удобнее шаблонные строки с обратными кавычками:

    Операторы и сравнения

    Арифметика

    Строгое и нестрогое сравнение

    В JavaScript есть два семейства операторов сравнения:

  • === и !==строгое сравнение (сравнивает и значение, и тип)
  • == и !=нестрогое сравнение (может приводить типы)
  • В обучении и в промышленной разработке обычно рекомендуют использовать === и !==, чтобы избегать скрытых преобразований.

    Пример:

    Логические операторы

  • && — И
  • || — ИЛИ
  • ! — НЕ
  • Условия: if, else if, else, switch

    if / else

    switch

    switch удобен, когда нужно сравнивать одно значение с несколькими вариантами:

    Циклы: for, while

    Циклы повторяют выполнение блока кода.

    for

    while

    Функции

    Функция — это фрагмент кода, который можно вызывать многократно.

    Объявление функции

    Функциональное выражение

    Стрелочная функция

    Стрелочные функции часто используют для коротких операций:

    Объекты и массивы: базовая работа

    Объект

    Объект хранит пары ключ–значение:

    Массив

    Массив — упорядоченный список:

    Ошибки новичков и полезные привычки

  • Используйте === вместо ==, чтобы избегать неожиданных преобразований типов
  • Начинайте с const, переходите на let только когда нужно переназначение
  • Делайте понятные имена переменных: userAge лучше, чем x
  • Читайте сообщения об ошибках в консоли: они часто прямо указывают строку и причину
  • Что дальше по курсу

    Дальше мы будем углубляться в практику:

  • Работа с DOM и событиями в браузере
  • Обработка данных: массивы, объекты, методы, итерации
  • Асинхронность: промисы, async/await, запросы в сеть
  • Структурирование кода и основы проектирования небольших приложений
  • 2. Функции, объекты и работа с данными

    Функции, объекты и работа с данными

    В предыдущей статье вы познакомились с базовым синтаксисом JavaScript: переменными, типами, условиями, циклами и простыми функциями. Теперь соберём это в практический набор инструментов, который чаще всего используется в реальных задачах: функции как единицы логики, объекты как модель данных и удобная обработка коллекций.

    Функции как основная единица логики

    Функция в JavaScript — это значение (как число или строка), которое можно:

  • присвоить переменной
  • передать в другую функцию
  • вернуть из другой функции
  • Это позволяет строить код из небольших переиспользуемых блоков.

    Параметры, аргументы и return

  • Параметры — переменные в объявлении функции.
  • Аргументы — конкретные значения при вызове.
  • return возвращает результат из функции и завершает её выполнение.
  • Если return не указан, функция возвращает undefined:

    Значения по умолчанию

    Иногда параметр может быть не передан. Для таких случаев есть значения по умолчанию:

    this в методе обычно указывает на объект, перед точкой которого произошёл вызов.

    Важно: стрелочную функцию как метод часто использовать нельзя, если внутри нужен this:

    Полезная справка:

  • MDN: Object.entries
  • Мини-практика: функция обработки данных

    Соберём небольшую «прикладную» функцию: посчитать сумму цен товаров, которые есть в наличии.

    Здесь хорошо видно разделение ответственности:

  • filter отвечает за отбор
  • map — за преобразование
  • reduce — за итоговое вычисление
  • Что дальше

    Теперь у вас есть базовый набор для работы с данными и построения логики:

  • функции и колбэки для переиспользуемого поведения
  • объекты для моделирования сущностей и данных
  • методы массивов для понятной обработки коллекций
  • Следующий логичный шаг в практике фронтенда — научиться брать данные из интерфейса и обновлять страницу: DOM, события и взаимодействие с браузером.

    3. DOM, события и взаимодействие с браузером

    DOM, события и взаимодействие с браузером

    В первых статьях курса вы освоили базовый синтаксис JavaScript, функции, объекты и методы массивов. Теперь переходим к тому, ради чего JavaScript чаще всего используют во фронтенде: управление страницей и реакция на действия пользователя.

    В этой статье вы научитесь:

  • понимать, что такое DOM и как устроена веб-страница с точки зрения браузера
  • находить элементы на странице и изменять их
  • создавать элементы из JavaScript
  • подписываться на события и писать обработчики
  • понимать всплытие событий и использовать делегирование
  • использовать несколько полезных браузерных API (таймеры, localStorage)
  • Что такое DOM

    DOM (Document Object Model) — это объектная модель HTML-документа. Браузер читает HTML и строит дерево узлов, с которыми можно работать из JavaScript.

  • Каждый HTML-элемент представлен объектом (узлом) в DOM.
  • Узлы связаны отношениями родитель–потомок.
  • JavaScript может читать и менять это дерево.
  • !Визуальное представление DOM как дерева узлов

    Полезная справка:

  • MDN: Введение в DOM
  • Как получать элементы со страницы

    querySelector и querySelectorAll

    Самый универсальный способ — CSS-селекторы.

  • querySelector возвращает первый найденный элемент или null.
  • querySelectorAll возвращает статическую коллекцию NodeList.
  • Полезная справка:

  • MDN: Document.querySelector
  • MDN: Document.querySelectorAll
  • Частая ошибка: элемент ещё не существует

    Если ваш скрипт выполняется раньше, чем браузер построил нужную часть DOM, селектор вернёт null.

    Надёжные варианты:

  • подключать скрипт в конце документа
  • ждать событие DOMContentLoaded
  • Чтение и изменение содержимого

    Текст и HTML

  • textContent безопасно устанавливает текст.
  • innerHTML вставляет HTML-строку и может быть опасен, если строка получена от пользователя.
  • Если вам нужно именно разметку собрать из данных, чаще лучше создавать элементы через createElement (см. ниже), а не через innerHTML.

    Полезная справка:

  • MDN: Node.textContent
  • MDN: Element.innerHTML
  • Атрибуты

    Для атрибутов используйте getAttribute и setAttribute.

    Полезная справка:

  • MDN: Element.getAttribute
  • MDN: Element.setAttribute
  • Классы

    Самый удобный способ работать с классами — classList.

    Полезная справка:

  • MDN: Element.classList
  • Стили

    Инлайн-стили доступны через style.

    Практическое правило:

  • для единичных динамических правок подходит style
  • для состояния интерфейса обычно лучше переключать классы через classList
  • Создание, вставка и удаление элементов

    Когда данные приходят из массива или объекта, типичный подход такой:

  • создать DOM-элементы
  • заполнить их данными
  • вставить в нужное место
  • createElement и append

    Полезная справка:

  • MDN: Document.createElement
  • MDN: Element.append
  • Удаление

    Полезная справка:

  • MDN: Element.remove
  • Практический пример: отрисовка списка из массива

    Свяжем это с предыдущей темой про массивы и map.

    Здесь:

  • forEach делает действие для каждого элемента массива
  • dataset хранит данные в DOM-элементе (удобно для обработчиков событий)
  • Полезная справка:

  • MDN: HTMLElement.dataset
  • События: как страница «реагирует»

    Событие — это сигнал от браузера, что что-то произошло.

  • пользователь кликнул мышью
  • ввёл текст
  • отправил форму
  • страница загрузилась
  • Чтобы обработать событие, мы подписываемся через addEventListener и передаём функцию-колбэк (это напрямую связано с предыдущей статьёй про функции как значения).

    addEventListener

    Полезная справка:

  • MDN: EventTarget.addEventListener
  • Объект события event

    Браузер передаёт в обработчик объект события.

  • event.target — элемент, на котором событие произошло
  • event.currentTarget — элемент, на который подписан обработчик
  • Полезная справка:

  • MDN: Event
  • Отмена действия по умолчанию: preventDefault

    Например, форма по умолчанию перезагружает страницу при отправке.

    Полезная справка:

  • MDN: Event.preventDefault
  • Всплытие событий и делегирование

    Всплытие

    Когда событие происходит на вложенном элементе, оно обычно «всплывает» вверх по дереву DOM: от цели события к родителям.

    !Как событие всплывает от вложенного элемента к родителям

    stopPropagation

    Иногда нужно остановить всплытие.

    Используйте stopPropagation аккуратно: он усложняет логику, если дальше вы захотите делегирование.

    Полезная справка:

  • MDN: Event.stopPropagation
  • Делегирование событий

    Если у вас список элементов, и вы хотите обрабатывать клики по каждому, не обязательно вешать обработчик на каждый элемент. Можно повесить один обработчик на родителя и проверять, по чему кликнули.

    Пример: удаление пользователя по клику на элемент списка.

    Важные детали:

  • closest('li') помогает найти нужный элемент, даже если внутри li появятся вложенные элементы
  • filter создаёт новый массив без удаляемого пользователя, что удобно для предсказуемой логики
  • Полезная справка:

  • MDN: Element.closest
  • Полезные браузерные API для взаимодействия

    DOM и события — основа, но на практике часто нужны и другие возможности браузера.

    Таймеры: setTimeout и setInterval

    Полезная справка:

  • MDN: setTimeout
  • MDN: setInterval
  • Хранение простых данных: localStorage

    localStorage хранит строки. Для объектов и массивов используйте JSON.stringify и JSON.parse.

    Полезная справка:

  • MDN: Window.localStorage
  • Мини-паттерн: разделяем состояние и отрисовку

    Когда интерфейс становится сложнее, помогает простой подход:

  • состояние хранится в переменных (например, массив задач)
  • отрисовка зависит только от состояния
  • события изменяют состояние и запускают отрисовку
  • Это не фреймворк, но уже приближает код к более “архитектурному” стилю.

    Частые ошибки и полезные привычки

  • Всегда проверяйте результат querySelector на null, если есть сомнения.
  • Предпочитайте textContent вместо innerHTML, если не нужна разметка.
  • Не вешайте много однотипных обработчиков на список элементов, если можно делегировать.
  • Храните данные в JavaScript, а DOM используйте как отображение данных.
  • Помните, что обработчик события — это колбэк: используйте функции из предыдущих тем, чтобы делать код модульным.
  • Что дальше

    Теперь вы умеете связывать JavaScript с интерфейсом: находить элементы, менять DOM и реагировать на события. Следующий шаг обычно такой:

  • получать данные из сети
  • работать с асинхронностью
  • собирать небольшие приложения, где данные приходят, отображаются и обновляются
  • Для справки по DOM и событиям:

  • MDN: Работа с DOM
  • MDN: Введение в события
  • 4. Асинхронность: промисы, async/await и таймеры

    Асинхронность: промисы, async/await и таймеры

    В прошлых статьях вы уже использовали колбэки (функции как значения) и работали с событиями в браузере. Следующий важный шаг в практике JavaScript — понять, как выполнять операции, которые завершаются не сразу: таймеры, запросы в сеть, чтение данных, ожидание действий пользователя.

    JavaScript выполняет код в одном потоке, поэтому длительные операции нельзя делать «в лоб», иначе интерфейс зависнет. Асинхронность решает это: мы запускаем действие, а результат получаем позже через колбэк или промис.

    Почему асинхронность нужна почти всегда

    Типичные источники задержки:

  • запросы по сети
  • ожидание таймера
  • работа с диском (в Node.js)
  • ожидание событий пользователя
  • Важно различать:

  • синхронный код выполняется сразу, строка за строкой
  • асинхронный код планирует работу и возвращает управление, а результат приходит позже
  • Модель выполнения: event loop простыми словами

    Браузер (или Node.js) выполняет JavaScript, используя цикл событий (event loop). Упрощённо это выглядит так:

  • есть стек вызовов (call stack), где выполняются функции
  • есть очередь задач (task queue), куда попадают события и таймеры
  • есть очередь микрозадач (microtask queue), куда попадают продолжения промисов (then, catch, finally, await)
  • Ключевое правило:

  • микрозадачи выполняются раньше, чем следующая задача из очереди задач
  • !Схема показывает, откуда берутся асинхронные продолжения и почему промисы выполняются раньше таймеров

    Полезные ссылки:

  • MDN: The event loop
  • MDN: setTimeout
  • Таймеры: setTimeout и setInterval

    setTimeout

    setTimeout планирует выполнение функции один раз через указанное время.

    Важные детали:

  • задержка — это не гарантия точности, а минимальное ожидание
  • фактический запуск зависит от того, насколько занят поток выполнения
  • Отменить таймер можно через clearTimeout:

    setInterval

    setInterval повторяет выполнение через заданный интервал.

    Практическое замечание:

  • если работа внутри колбэка тяжёлая, интервалы могут «съезжать»
  • часто надёжнее делать «интервал» через рекурсивный setTimeout
  • Промисы: что это и зачем

    Промис (Promise) — объект, который представляет результат асинхронной операции.

    У промиса есть состояния:

  • pending — ожидание
  • fulfilled — выполнен успешно
  • rejected — выполнен с ошибкой
  • Создание промиса обычно выглядит так:

    then, catch, finally и цепочки

    Промисы удобно цепочить: каждый then возвращает новый промис.

    Правила, которые стоит запомнить:

  • если из then вернуть значение, следующий then получит это значение
  • если из then вернуть промис, следующий then подождёт его
  • если внутри then выбросить ошибку, управление перейдёт в ближайший catch
  • Пример цепочки:

    async/await: синхронный стиль поверх промисов

    async/await — это синтаксис, который делает работу с промисами похожей на обычный последовательный код.

    Ключевые правила:

  • async-функция всегда возвращает промис
  • await можно использовать только внутри async-функции
  • await «ждёт» промис и возвращает его результат, либо выбрасывает ошибку при reject
  • Пример:

    Обработка ошибок: try/catch

    Практическая привычка:

  • если функция может упасть, решите заранее: вы пробрасываете ошибку наверх или превращаете её в безопасный результат (null, пустой массив, объект с ok: false)
  • Полезная ссылка:

  • MDN: async function
  • Микрозадачи и порядок выполнения: промисы против таймеров

    Из-за очереди микрозадач иногда кажется, что промисы «быстрее таймеров». Они не быстрее, просто у них приоритет выполнения продолжений.

    Пример порядка логов:

    Обычно вывод будет таким:

  • A
  • D
  • C
  • B
  • Пояснение:

  • синхронный код (A, D) выполняется сразу
  • then попадает в микрозадачи и выполняется перед следующей задачей
  • setTimeout попадает в очередь задач и выполняется позже
  • Несколько промисов вместе: all, allSettled, race, any

    Promise.all

    Promise.all([...]) ждёт, пока выполнятся все промисы, и возвращает массив результатов.

    Особенность:

  • если хотя бы один промис завершится с ошибкой, весь Promise.all сразу перейдёт в rejected
  • Promise.allSettled

    Promise.allSettled([...]) ждёт завершения всех промисов и возвращает их статусы.

    Полезно, когда важно собрать все результаты, даже если часть упала.

    Полезные ссылки:

  • MDN: Promise.all
  • MDN: Promise.allSettled
  • MDN: Promise.race
  • MDN: Promise.any
  • Практический пример: запрос данных и обновление DOM

    Свяжем асинхронность с темой DOM и событий: по клику загружаем данные и показываем статус.

    Правильные варианты:

    Что дальше

    Теперь у вас есть базовая «практическая» модель асинхронности:

  • таймеры как источник отложенных действий
  • промисы как стандартный контейнер результата
  • async/await как удобная запись промисов
  • понимание очередей задач и микрозадач для объяснения порядка выполнения
  • Следующий шаг в практике — собрать небольшое приложение, где данные загружаются, отображаются, кешируются (например, через localStorage) и обновляются по действиям пользователя.

    5. Работа с API: Fetch, JSON и основы архитектуры

    Работа с API: Fetch, JSON и основы архитектуры

    На предыдущем шаге вы разобрались с асинхронностью: промисы, async/await, таймеры и общий принцип запустили сейчас — получили результат позже. Теперь применим это к одной из самых частых задач в веб-разработке: получать данные с сервера и отображать их в интерфейсе.

    В этой статье вы научитесь:

  • понимать, что такое API, HTTP-запрос и ответ
  • делать запросы через fetch
  • правильно работать с JSON (JSON.parse, JSON.stringify, response.json())
  • обрабатывать ошибки сети и HTTP
  • отменять запросы через AbortController
  • организовывать код так, чтобы его было проще поддерживать: разделение ответственности и простая архитектура
  • Что такое API и как браузер общается с сервером

    API (Application Programming Interface) — это способ, которым одна программа предоставляет функции другой. В контексте фронтенда чаще всего речь про веб-API сервера: набор адресов (URL), по которым можно получить или отправить данные.

    Когда вы делаете запрос к серверу, обычно участвуют:

  • URL — адрес ресурса, например /api/todos
  • HTTP-метод — действие:
  • - GET получить данные - POST создать - PUT или PATCH обновить - DELETE удалить
  • заголовки (headers) — метаданные запроса/ответа (например, тип данных)
  • тело (body) — данные, которые вы отправляете (часто в JSON)
  • !Упрощённая схема HTTP-запроса и ответа

    Полезные ссылки:

  • MDN: HTTP overview
  • MDN: HTTP response status codes
  • JSON: формат данных для обмена

    JSON (JavaScript Object Notation) — текстовый формат для передачи данных. Он похож на объекты JavaScript, но это именно строка.

    Превратить объект в JSON-строку

    Превратить JSON-строку в объект

    Практические правила:

  • JSON.stringify нужен перед отправкой объекта в fetch, если сервер ждёт JSON
  • JSON.parse нужен, когда вы получили JSON-строку (например, из localStorage)
  • Полезная ссылка:

  • MDN: Working with JSON
  • Fetch: базовый инструмент запросов в браузере

    fetch — встроенная браузерная функция для HTTP-запросов. Она возвращает промис, поэтому с ней удобно работать через async/await.

    Простой GET запрос

    Что здесь происходит:

  • fetch(...) возвращает промис, который выполнится, когда придёт HTTP-ответ
  • res — объект Response (ответ)
  • res.json() читает тело ответа и парсит JSON, возвращая промис с объектом
  • Полезные ссылки:

  • MDN: fetch
  • MDN: Response
  • Важный нюанс: fetch не считает HTTP 404/500 ошибкой промиса

    fetch отклоняет промис (попадает в catch) в основном при сетевых проблемах (например, нет интернета). Но HTTP-ошибки вроде 404 или 500 считаются корректным ответом сервера.

    Поэтому проверка res.ok почти всегда обязательна:

    Полезная ссылка:

  • MDN: Request
  • Ошибки и устойчивость: try/catch, сообщения пользователю, повтор

    В интерфейсе важно отличать:

  • загрузка (показываем “Loading...”, блокируем кнопку)
  • успех (рисуем данные)
  • ошибка (показываем понятное сообщение и даём повторить)
  • Пример функции, которая превращает любые проблемы в понятную ошибку:

    Детали:

  • signal связывает запрос с контроллером
  • controller.abort() прерывает запрос, и промис отклоняется
  • Полезная ссылка:

  • MDN: AbortController
  • Основы архитектуры: как не превратить проект в “спагетти”

    Когда вы добавляете DOM, события и fetch в один файл, код быстро усложняется. Чтобы проще развивать приложение, полезно разделять ответственность.

    Принцип разделения ответственности

    Обычно удобно разделять код на слои:

  • API-клиент: знает, как ходить в сеть и проверять res.ok
  • логика: решает, что и когда загружать, как преобразовать данные
  • UI (DOM): отвечает только за отображение и подписку на события
  • !Разделение ответственности между UI, логикой и API

    Плюсы:

  • легче тестировать: API можно подменить заглушкой
  • легче менять: например, заменить localStorage на другой способ хранения
  • легче читать: в UI не смешаны детали HTTP
  • Минимальный “каркас” приложения

    Соберём небольшой пример: по кнопке загрузить список постов, показать их и обработать ошибку.

    #### API-слой

    Что важно в этой структуре:

  • UI-слой не знает, что такое res.ok, JSON и URL-параметры
  • API-слой ничего не знает про DOM
  • логика может преобразовывать данные под нужды интерфейса
  • Небольшая практика архитектуры: кеш в localStorage

    Если данные редко меняются, иногда выгодно кешировать результат. Напомним: localStorage хранит только строки, значит нужен JSON.

    Практические замечания:

  • кеш нужно уметь инвалидировать: например, хранить версию ключа (posts_cache_v1)
  • в реальных проектах добавляют время жизни кеша (TTL), но для начала достаточно версии
  • Частые ошибки при работе с API

  • ожидать, что fetch упадёт на HTTP 404/500, и не проверять res.ok
  • забывать await res.json() и пытаться работать с промисом как с данными
  • смешивать сетевой код и DOM-отрисовку в одном месте, из-за чего сложно расширять приложение
  • не думать про отмену запросов в сценариях поиска и быстрого ввода
  • Что дальше

    Теперь у вас есть полный “мост” от асинхронности к реальной разработке:

  • fetch для запросов
  • JSON для обмена данными
  • обработка ошибок и отмена запросов
  • базовая архитектура: разделение на API, логику и UI
  • С этими знаниями вы уже можете собирать небольшие приложения: загрузка данных, отображение, обновление по событиям и кеширование.