Frontend-разработчик с нуля: путь до стажировки

Практический курс для начинающих frontend-разработчиков, который системно проведёт от основ HTML и CSS через JavaScript и React до реальной подготовки к стажировке. Охватывает ключевые инструменты, профессиональные подходы и типичные задачи junior-уровня.

1. Основы HTML и современный CSS: семантика, Flexbox, Grid, адаптивная вёрстка и анимации

Основы HTML и современный CSS: семантика, Flexbox, Grid, адаптивная вёрстка и анимации

Почему две страницы с одинаковым содержимым выглядят настолько по-разному: одна — как профессиональный сайт, а другая — как набросок из 2005 года? Ответ кроется не в дизайнерских талантах, а в умении правильно выбрать HTML-теги и грамотно управлять раскладкой через CSS. Именно эти два навыка — фундамент, на котором строится всё остальное в frontend-разработке.

Семантический HTML: не просто теги, а смысл

Когда вы оборачиваете весь контент страницы в <div> за <div>, браузер видит структуру, но не понимает её. Семантический HTML — это использование тегов, которые описывают роль контента, а не только его внешний вид. Разница между <div class="header"> и <header> кажется косметической, но она критична для трёх групп: поисковых роботов, скринридеров и будущих разработчиков, которые будут читать ваш код.

> Семантическая вёрстка — это когда HTML-теги говорят «что это», а CSS говорит «как это выглядит».

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

  • <header> — шапка страницы или секции
  • <nav> — блок навигации
  • <main> — основное содержимое страницы (один на страницу)
  • <section> — тематический раздел контента
  • <article> — самодостаточный контент (пост, статья, комментарий)
  • <aside> — побочный контент (сайдбар, врезка)
  • <footer> — подвал страницы или секции
  • Микропример: блок новостей на сайте — это <section>, каждая отдельная новость внутри него — <article>, а список категорий справа — <aside>.

    Почему это важно на практике? Скринридеры используют семантическую структуру для навигации: пользователь может перейти от <nav> к <main>, пропустив <header>. Без семантики весь контент для вспомогательных технологий — однородная масса. А поисковые системы дают больший вес контенту внутри <main> и <article>, чем внутри произвольного <div>.

    CSS-раскладка: от Float к Flexbox и Grid

    Исторически вёрстка на CSS держалась на хаках: float, position: absolute, таблицы. Эти подходы работали, но превращали простые задачи в головоломки. Flexbox и Grid — современные системы раскладки, каждая из которых решает свой класс задач.

    Flexbox (Flexible Box Layout) управляет раскладкой в одном направлении — по горизонтали или по вертикали. Представьте, что вы раскладываете книги на полке: Flexbox решает, сколько места заняла каждая книга, как распределить пустое пространство и что делать, если книг больше, чем места.

    Ключевые свойства Flexbox:

    | Свойство | Что делает | Частые значения | |---|---|---| | flex-direction | Направление оси | row, column | | justify-content | Распределение по главной оси | center, space-between, space-around | | align-items | Выравнивание по поперечной оси | center, stretch, flex-start | | flex-wrap | Перенос элементов | wrap, nowrap | | gap | Отступ между элементами | 8px, 1rem |

    Grid управляет раскладкой в двух направлениях одновременно — по строкам и столбцам. Это как разметка газетной полосы: вы заранее определяете сетку, а контент распределяется по ячейкам.

    Когда использовать Flexbox, а когда Grid? Flexbox — для компонентов: навигационная панель, карточка товара, кнопки в ряд. Grid — для макета страницы: шапка-сайдбар-контент-подвал, каталог товаров, галерея. На практике они часто работают вместе: Grid задаёт общий макет, а внутри каждой области Flexbox раскладывает элементы.

    Адаптивная вёрстка: один сайт для всех экранов

    Адаптивная вёрстка (responsive design) — подход, при котором одна и та же страница корректно отображается на экранах любого размера: от смартфона до широкоформатного монитора. Основной инструмент — медиа-запросы (media queries), которые применяют CSS-правила при определённых условиях.

    Три принципа адаптивной вёрстки, которые должен знать каждый стажёр:

  • Mobile-first — начинайте стилизацию с мобильной версии, затем добавляйте сложность через min-width медиа-запросы. Это проще и производительнее, чем упрощать десктопную вёрстку для мобильных.
  • Относительные единицы — используйте rem для размеров шрифтов и vw/vh для вьюпортов вместо фиксированных px там, где нужна гибкость.
  • Гибкие изображенияmax-width: 100% и height: auto предотвращают выход изображений за пределы контейнера.
  • Микропример: навигация с пятью пунктами на десктопе — горизонтальный ряд через Flexbox. На экране меньше 768px — превращается в вертикальное меню или «гамбургер»-кнопку.

    CSS-анимации: оживляем интерфейс

    Анимации в CSS бывают двух видов: переходы (transitions) и ключевые кадры (keyframes). Переходы плавно меняют свойство из одного состояния в другое — например, кнопка при наведении. Ключевые кадры позволяют задать сложную последовательность изменений.

    Для более сложных сценариев — @keyframes:

    Важное правило: анимируйте только transform и opacity — эти свойства не вызывают перерисовку макета (reflow) и обрабатываются GPU. Анимация width, height, margin или top/left заставляет браузер пересчитывать положение всех элементов на странице, что заметно снижает производительность.

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

    Разберём типичную задачу стажёра — сверстать карточку товара для каталога.

    Семантическая структура:

    CSS с Flexbox, адаптивностью и анимацией:

    А теперь — каталог из таких карточек через Grid:

    repeat(auto-fill, minmax(280px, 1fr)) — это одна из самых полезных конструкций в CSS Grid: она автоматически создаёт столько колонок, сколько помещается, каждая шириной минимум 280px, а свободное пространство распределяется равномерно. Вам не нужно писать отдельные медиа-запросы для каждого количества колонок.

    Распространённые ошибки начинающих

    Ошибка 1: <div> вместо семантических тегов. Код работает, но становится нечитаемым и недоступным. Каждый <div> с классом header, footer, nav — сигнал, что нужно заменить его на семантический тег.

    Ошибка 2: Фиксированные размеры вместо гибких. width: 960px на контейнере — это вёрстка, которая сломается на мобильном. Используйте max-width и относительные единицы.

    Ошибка 3: Злоупотребление !important. Если вы пишете !important, значит, проблема в специфичности селекторов, а не в стиле. Пересмотрите структуру CSS.

    Ошибка 4: Игнорирование gap в Flexbox и Grid. Раньше отступы между элементами делали через margin, что приводило к лишним отступам у последнего элемента. Свойство gap решает эту проблему элегантно.

    Если из этой статьи запомнить три вещи — это: семантические теги делают код доступным и читаемым; Flexbox для компонентов, Grid для макета; анимируйте только transform и opacity.

    2. JavaScript с нуля до DOM и событий: переменные, функции, работа с деревом элементов

    JavaScript с нуля до DOM и событий: переменные, функции, работа с деревом элементов

    Статическая HTML-страница — это как витрина магазина: вы можете смотреть, но не взаимодействовать. JavaScript превращает витрину в интерактивный магазин: появляются кнопки, которые что-то делают, формы, которые проверяют ввод, и контент, который меняется без перезагрузки. Именно JavaScript — язык, который оживляет веб-страницы, и это главный инструмент frontend-разработчика.

    Переменные и типы данных

    Переменная — это именованная ячейка памяти, в которой хранится значение. Представьте коробку с наклейкой: на наклейке — имя переменной, внутри — значение.

    В JavaScript есть три способа объявить переменную: var, let и const. На практике используйте только let (для значений, которые будут меняться) и const (для значений, которые не изменятся). Ключевое слово var — устаревший подход с неочевидным поведением, от которого современный код отказывается.

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

    Основные типы данных:

  • Строки (string) — текст: "Привет", 'Мир', шаблонные строки ` Страница {name}!;
  • return message; }

    console.log(message); // Ошибка: message не определена вне функции javascript const expensiveNames = cart .filter(item => item.price > 2000) .map(item => item.name); // ['Наушники'] javascript const user = { name: 'Аня', age: 25, city: 'Москва' }; const { name, age } = user; // name = 'Аня', age = 25 javascript // По ID — возвращает один элемент const header = document.getElementById('header');

    // По CSS-селектору — возвращает первый подходящий const button = document.querySelector('.submit-btn');

    // По CSS-селектору — возвращает все подходящие (коллекция) const items = document.querySelectorAll('.product-card'); javascript const title = document.querySelector('h1'); title.textContent = 'Новый заголовок'; // изменить текст title.classList.add('highlighted'); // добавить CSS-класс title.setAttribute('data-id', '42'); // установить атрибут javascript const newParagraph = document.createElement('p'); newParagraph.textContent = 'Добавленный абзац'; document.querySelector('.content').appendChild(newParagraph); javascript const button = document.querySelector('.submit-btn');

    button.addEventListener('click', function(event) { console.log('Кнопка нажата!'); console.log(event.target); // элемент, на котором произошёл клик }); javascript const catalog = document.querySelector('.catalog');

    catalog.addEventListener('click', function(event) { const card = event.target.closest('.product-card'); if (!card) return; // клик был не по карточке

    const productName = card.querySelector('.product-card__title').textContent; console.log(Выбран товар: ${productName}); }); html <div class="counter"> <span class="counter__value">0</span> <div class="counter__controls"> <button class="counter__btn" data-action="decrement">−</button> <button class="counter__btn" data-action="increment">+</button> <button class="counter__btn" data-action="reset">Сброс</button> </div> </div> javascript const valueDisplay = document.querySelector('.counter__value'); const controls = document.querySelector('.counter__controls');

    let count = 0;

    function updateDisplay() { valueDisplay.textContent = count; }

    controls.addEventListener('click', function(event) { const action = event.target.dataset.action; if (!action) return;

    switch (action) { case 'increment': count++; break; case 'decrement': count--; break; case 'reset': count = 0; break; }

    updateDisplay(); }); `

    Здесь использовано делегирование: один обработчик на контейнере кнопок. Атрибут data-action определяет, какое действие выполнить. Функция updateDisplay отвечает за обновление отображения — принцип разделения ответственности.

    Типичные ошибки

    Ошибка 1: var вместо let/const. Переменные через var «всплывают» (hoisting) и доступны за пределами ожидаемой области видимости, что приводит к трудноуловимым багам.

    Ошибка 2: Манипуляция DOM в цикле без необходимости. Вставка 100 элементов по одному вызывает 100 перерисовок. Соберите HTML-строку или используйте DocumentFragment, а затем вставьте всё за один раз.

    Ошибка 3: Сравнение с == вместо ===. Оператор == приводит типы перед сравнением (0 == falsetrue), что вызывает неожиданное поведение. Всегда используйте строгое сравнение ===.

    Если из этой главы запомнить три вещи — это: используйте const и let, забудьте про var`; DOM — это дерево, и JavaScript может находить и изменять любой узел; делегирование событий экономит память и работает с динамическими элементами.

    3. Асинхронный JavaScript и работа с API: промисы, async/await, fetch и обработка данных

    Асинхронный JavaScript и работа с API: промисы, async/await, fetch и обработка данных

    Представьте: пользователь нажимает кнопку «Загрузить ещё», и страница зависает на три секунды, пока браузер ждёт ответ от сервера. Курсор превращается в спиннер, ничего нельзя нажать, а пользователь думает, что сайт сломался. Именно так работал бы JavaScript без асинхронности — и именно поэтому понимание асинхронных операций отделяет новичка от разработчика, готового к стажировке.

    Почему JavaScript не может ждать

    JavaScript — однопоточный язык: он выполняет одну операцию за раз. Когда браузер отправляет запрос на сервер, ответ может прийти через 100 миллисекунд, а может — через 5 секунд. Если бы JavaScript просто «спал» всё это время, интерфейс был бы полностью заблокирован: никаких кликов, анимаций, прокрутки.

    Решение — асинхронная модель: JavaScript отправляет запрос и продолжает работать. Когда ответ приходит, браузер вызывает специальную функцию — колбэк (callback). Это как заказ в ресторане: вы делаете заказ, сидите за столом и можете болтать с друзьями. Когда еда готова, официант приносит её — это и есть «вызов колбэка».

    Промисы: обещания, которые выполняются

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

  • pending (ожидание) — операция ещё выполняется
  • fulfilled (выполнено) — операция завершилась успешно
  • rejected (отклонено) — произошла ошибка
  • Микропример: промис — это как посылка с трекингом. Когда вы заказываете товар, у вас есть трек-номер (промис). Посылка может быть «в пути» (pending), «доставлена» (fulfilled) или «возвращена отправителю» (rejected). Метод .then() — это ваша реакция на доставку, .catch() — реакция на проблему.

    Методы .then() можно цеплять друг за друга — это называется чейнинг (chaining):

    Критически важный момент: fetch не выбрасывает ошибку при HTTP-статусах 4xx или 5xx. Он считает ошибкой только сетевые проблемы (нет соединения, DNS не найден). Поэтому проверка response.ok — обязательный шаг.

    POST-запрос с отправкой данных:

    Три уровня обработки: (1) try/catch ловит сетевые ошибки, (2) проверка response.ok ловит серверные ошибки, (3) fallback-данные обеспечивают работу интерфейса даже при полном отказе API.

    Практический кейс: загрузка и отображение списка пользователей

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

    Важный нюанс: Promise.all отклоняет весь промис, если хотя бы один из запросов упал. Если нужно, чтобы остальные данные загрузились даже при ошибке одного запроса, используйте Promise.allSettled:

    Распространённые ошибки

    Ошибка 1: Забытый await. Если вы напишете const data = fetch(url) без await, в data окажется промис, а не данные. Ошибка не выбросится сразу — она проявится, когда вы попытаетесь обратиться к свойствам «данных».

    Ошибка 2: await в цикле без необходимости. Если запросы независимы, используйте Promise.all вместо последовательного for...of с await — это в разы быстрее.

    Ошибка 3: Игнорирование response.ok. Fetch не считает 404 или 500 ошибкой. Без проверки response.ok вы будете пытаться распарсить HTML-страницу ошибки как JSON — и получите непонятный баг.

    Если из этой главы запомнить три вещи — это: async/await делает асинхронный код читаемым; fetch не выбрасывает ошибку при HTTP-ошибках — проверяйте response.ok; Promise.all для параллельных запросов, Promise.allSettled — когда важна устойчивость к ошибкам.

    4. Введение в React и создание приложений: компоненты, состояние, маршрутизация и хуки

    Введение в React и создание приложений: компоненты, состояние, маршрутизация и хуки

    Представьте, что вы строите дом из LEGO. Каждый кубик — это законченный элемент: стена, окно, дверь. Вы можете собрать дом любого размера, комбинируя одни и те же кубики по-разному. React работает точно так же: вы создаёте переиспользуемые компоненты и собираете из них интерфейс любой сложности. Именно этот подход — от маленьких, независимых частей к большому целому — сделал React самым популярным фреймворком для frontend-разработки.

    Зачем нужен фреймворк

    Ванильный JavaScript с DOM-манипуляциями отлично работает для простых страниц. Но когда интерфейс содержит сотни элементов, которые зависят друг от друга и обновляются при каждом действии пользователя, код превращается в клубок из querySelector, createElement и addEventListener. React решает эту проблему через декларативный подход: вы описываете, как должен выглядеть интерфейс при определённых данных, а фреймворк сам разбирается, какие DOM-элементы обновить.

    > В императивном подходе (ванильный JS) вы говорите: «Найди элемент, измени текст, добавь класс». В декларативном (React) вы говорите: «При таком состоянии — интерфейс выглядит вот так».

    Компоненты: строительные блоки

    Компонент в React — это JavaScript-функция, которая возвращает разметку (JSX). Каждый компонент — это самодостаточный фрагмент интерфейса со своей логикой и отображением.

    JSX — это расширение синтаксиса JavaScript, которое позволяет писать HTML-подобную разметку прямо в коде. Под капотом JSX трансформируется в вызовы React.createElement, но на практике вы просто пишете разметку, похожую на HTML, с возможностью встраивать JavaScript-выражения в фигурных скобках.

    Микропример: компонент Greeting выше — это как шаблон открытки. Вы вставляете его в любое место приложения, передаёте不同的 имя — и получаете персонализированное приветствие. Один компонент, множество использований.

    Компоненты вкладываются друг в друга, как матрёшка:

    Состояние: данные, которые меняются

    Состояние (state) — это данные компонента, которые могут изменяться со временем и при изменении которых React обновляет интерфейс. Без состояния компонент — статичная картинка. Состояние превращает его в живой элемент.

    Хук useState — основной способ работы с состоянием в функциональных компонентах:

    useState(0) возвращает массив из двух элементов: текущее значение (count) и функцию для его изменения (setCount). Начальное значение — 0. Когда вы вызываете setCount(5), React обновляет состояние и перерисовывает компонент с новым значением.

    Важное правило: никогда не изменяйте состояние напрямую. Не пишите count = 5 — это не вызовет перерисовку. Всегда используйте функцию-сеттер.

    Если новое состояние зависит от предыдущего, используйте функциональную форму:

    Пропсы: передача данных между компонентами

    Пропсы (props) — это параметры, которые родительский компонент передаёт дочернему. Это как аргументы функции: вы вызываете компонент и передаёте ему данные через атрибуты.

    Пропсы передаются только от родителя к ребёнку — это однонаправленный поток данных. Дочерний компонент не может изменить пропсы, но может вызвать переданную функцию-колбэк, чтобы сообщить родителю о событии.

    Микропример: представьте ресторан. Официант (родитель) передаёт заказ повару (ребёнок). Повар не может изменить заказ, но может вернуть блюдо — это колбэк.

    Хуки: useEffect и жизненный цикл

    useEffect — хук для выполнения побочных эффектов: загрузка данных, подписка на события, работа с таймерами. Всё, что выходит за рамки рендеринга интерфейса.

    Массив зависимостей (второй аргумент useEffect) определяет, когда эффект перезапускается:

  • [] — один раз при монтировании компонента
  • [userId] — при каждом изменении userId
  • без массива — после каждого рендера (редко используется)
  • Важный аспект: если эффект создаёт подписку или таймер, верните функцию очистки:

    Списки и ключи

    React отрисовывает списки через метод map(). Каждый элемент списка должен получить уникальный атрибут key — он помогает React определить, какие элементы изменились, добавились или удалились:

    Распространённая ошибка — использовать индекс массива в качестве ключа (key={index}). Если элементы списка могут переупорядочиваться или удаляться, индекс меняется, и React неправильно сопоставляет элементы с DOM-узлами. Всегда используйте стабильный уникальный идентификатор из данных.

    Маршрутизация: навигация между страницами

    Одностраничные приложения (SPA) не перезагружают страницу при переходе между разделами. React Router — библиотека, которая управляет навигацией в таких приложениях.

    Компонент <Link> вместо обычной ссылки <a> — он предотвращает перезагрузку страницы и обновляет URL через History API. Маршрут path="*" — обработка несуществующих адресов (страница 404).

    Для динамических маршрутов используется параметр:

    Практический кейс: простое приложение «Список задач»

    Соберём приложение с добавлением, отметкой и удалением задач:

    Обратите внимание на иммутабельность: функции toggleTodo и deleteTodo не изменяют существующий массив, а создают новый через map() и filter(). Это фундаментальный принцип React — состояние должно обновляться через создание новых данных, а не через мутацию существующих.

    Распространённые ошибки

    Ошибка 1: Мутация состояния. todos.push(newTodo) не вызовет перерисовку. Используйте setTodos([...todos, newTodo]) — spread-оператор создаёт новый массив.

    Ошибка 2: Отсутствие key в списках. Без ключей React не может эффективно обновлять списки, что приводит к багам и снижению производительности.

    Ошибка 3: Бесконечный цикл в useEffect. Если эффект обновляет состояние, а это состояние в массиве зависимостей — эффект перезапускается бесконечно. Тщательно проверяйте массив зависимостей.

    Если из этой главы запомнить три вещи — это: React строит интерфейс из компонентов, которые принимают пропсы и хранят состояние; состояние обновляется иммутабельно — через создание новых данных; useEffect для побочных эффектов, а массив зависимостей определяет частоту его вызова.

    5. Подготовка к стажировке: Git, лучшие практики, доступность, производительность и собеседования

    Подготовка к стажировке: Git, лучшие практики, доступность, производительность и собеседования

    Что отличает кандидата, которого берут на стажировку, от десятков других с таким же резюме? Не количество выученных технологий, а умение работать в команде, писать читаемый код и понимать, что важно для реального продукта. Эта статья — финальный блок курса, который соберёт все навыки воедино и подготовит вас к первому дню в команде разработки.

    Git: контроль версий как рабочий инструмент

    Git — система контроля версий, которая отслеживает изменения в коде. Без Git работа в команде невозможна: два разработчика не могут одновременно править один файл, не зная о правках друг друга. Git решает эту проблему через ветвление (branching) и слияние (merging).

    Базовый рабочий цикл, который вы будете повторять ежедневно:

    После push вы создаёте pull request (PR) — запрос на слияние вашей ветки с основной. Это код-ревью: коллеги проверяют ваш код, оставляют комментарии, вы вносите правки. Именно через код-ревью вы будете учиться быстрее всего.

    > Хороший commit-месседж — это не «фикс» или «обновления», а конкретное описание: «Исправить валидацию email при пустом поле».

    Соглашение о commit-сообщениях, которое принято в большинстве команд:

    | Префикс | Когда использовать | Пример | |---|---|---| | feat: | Новый функционал | feat: добавить фильтрацию по дате | | fix: | Исправление бага | fix: сброс формы при ошибке сервера | | refactor: | Рефакторинг без изменения поведения | refactor: вынести API-клиент в отдельный модуль | | docs: | Изменения в документации | docs: обновить README |

    Микропример: вы работаете над задачей «Добавить корзину». Создаёте ветку feature/cart, делаете 5 коммитов с префиксом feat:, открываете PR, получаете замечания, исправляете, и ментор мержит ваш код в main. Вы видите свой код в продакшене — это лучшая мотивация.

    Лучшие практики написания кода

    Код, который вы пишете на стажировке, будут читать другие люди. Читаемость важнее краткости — это правило, которое отличает профессионала от любителя.

    Именование. Переменные и функции должны говорить, что они делают, а не как:

  • Плохо: const d = new Date(); const x = d.getDate();
  • Хорошо: const currentDate = new Date(); const dayOfMonth = currentDate.getDate();
  • Разделение ответственности. Функция должна делать одну вещь. Если функция loadAndRenderUsers загружает данные, форматирует HTML и вешает обработчики — разбейте её на три отдельные функции.

    Константы вместо магических значений. Если в коде встречается число 86400 — вынесите его в константу const SECONDS_IN_DAY = 86400. Через месяц вы забудете, что означает это число.

    Структура проекта. Организуйте файлы по смыслу, а не по типу:

    Каждый компонент — в своей папке с JSX и CSS. Утилитарные функции — в папке utils. Страницы — в pages. Эта структура масштабируется: добавить новый компонент — значит создать папку, а не искать, куда засунуть файл.

    Доступность (a11y): веб для всех

    Доступность (accessibility, сокращённо a11y) — это обеспечение возможности использования веб-сайта людьми с ограниченными возможностями. По данным ВОЗ, более миллиарда людей в мире живут с той или иной формой инвалидности. Доступность — не «приятное дополнение», а юридическое требование во многих странах и профессиональная обязанность разработчика.

    Три базовых принципа, которые должен знать каждый стажёр:

    1. Альтернативный текст для изображений. Скринридер не может «увидеть» картинку. Атрибут alt сообщает, что на ней изображено:

    Если изображение чисто декоративное, используйте пустой alt="" — скринридер пропустит его.

    2. Контрастность текста. Текст должен быть читаемым на фоне. Стандарт WCAG требует соотношение контрастности минимум для обычного текста и для крупного. Светло-серый текст на белом фоне — типичная ошибка, которая делает контент недоступным.

    3. Управление с клавиатуры. Все интерактивные элементы должны быть доступны через Tab и Enter. Если вы делаете кастомную кнопку из <div> — добавьте role="button", tabindex="0" и обработку нажатия Enter. Но лучше используйте настоящий <button> — он уже имеет всю необходимую функциональность.

    Микропример: выпадающее меню, которое открывается только при клике мышью, недоступно для пользователя, который управляет компьютером с клавиатуры. Добавьте обработку клавиши Escape для закрытия и Tab для навигации по пунктам.

    Производительность: быстрые страницы

    Пользователь покидает страницу, если она загружается дольше 3 секунд. Производительность frontend — это не абстрактная оптимизация, а直接影响 конверсии и удержания аудитории.

    Четыре конкретных приёма, которые вы можете применить прямо сейчас:

    1. Оптимизация изображений. Изображения — самый тяжёлый ресурс на большинстве страниц. Используйте формат WebP (на 25–35% легче JPEG), указывайте размеры через width и height (предотвращает сдвиг макета при загрузке) и применяйте loading="lazy" для изображений ниже видимой области:

    2. Минимизация перерисовок DOM. Группируйте изменения DOM: вместо 10 вставок элементов по одному используйте DocumentFragment или собираете HTML-строку и вставляете за один раз.

    3. Отложенная загрузка скриптов. Атрибут defer загружает скрипт параллельно с HTML и выполняет после парсинга страницы. Атрибут async — загружает и выполняет немедленно, не дожидаясь парсинга. Для основного скрипта приложения обычно используют defer.

    4. Код-сплиттинг. Не загружайте весь код приложения сразу. React поддерживает динамический импорт — код страницы загружается только когда пользователь переходит на неё:

    Подготовка к собеседованию

    Собеседование на позицию стажёра frontend-разработчика обычно состоит из трёх этапов: проверка резюме, техническое интервью и иногда небольшое тестовое задание.

    Что спрашивают на техническом интервью:

  • Разница между let, const и var
  • Как работает прототипное наследование в JavaScript
  • Что такое замыкание (closure) и зачем оно нужно
  • Чем === отличается от ==
  • Как работает Virtual DOM в React
  • Разница между useEffect и useLayoutEffect
  • Что такое CORS и почему возникают ошибки при запросах к API
  • Как готовиться эффективно:

  • Практикуйтесь в объяснении вслух. Знание — это не только уметь написать код, но и объяснить, почему он работает именно так. Объясняйте свой код другу, ментору или даже воображаемому собеседнику.
  • Решайте задачи на манипуляцию DOM. Типичное задание: «Сверстать карточку товара», «Сделать туду-лист», «Реализовать аккордеон». Убедитесь, что вы можете сделать это без подглядывания в документацию.
  • Читайте чужой код. Откройте любой open-source проект на GitHub и попробуйте разобраться, как устроен конкретный компонент. Это развивает навык, который нужен на код-ревью.
  • Готовьте вопросы работодателю. «Как устроен процесс код-ревью?», «Есть ли ментор у стажёров?», «Над каким проектом я буду работать?» — эти вопросы показывают зрелость и интерес.
  • Типичная ошибка на собеседовании: молчать, когда не знаешь ответа. Говорите: «Я не уверен, но предполагаю, что...» или «Я не сталкивался с этим, но как я понимаю...». Это лучше, чем молчание или попытка выдумать ответ.

    Рабочий процесс в команде

    Первые недели в команде — самые сложные. Вот что ожидать и как адаптироваться:

  • Ежедневные стендапы — короткие встречи (5–15 минут), где каждый рассказывает, что делал вчера, что будет делать сегодня, и есть ли блокеры.
  • Задачи в трекере (Jira, Linear, GitHub Issues) — каждая задача имеет описание, приоритет и статус. Не начинайте работу, пока не поймёте, что именно нужно сделать.
  • Код-ревью — ваши PR будут проверять. Комментарии — это не критика, а обучение. Каждое замечание — возможность стать лучше.
  • Микропример: ментор оставляет в вашем PR комментарий «Здесь лучше использовать useCallback для стабильной ссылки на функцию». Вы не знаете, что такое useCallback — googlите, читаете документацию, спрашиваете ментора, исправляете. Так и происходит рост.

    Если из этой главы запомнить три вещи — это: Git и pull request — это не просто инструменты, а основа совместной работы; доступность — обязанность разработчика, а не опция; на собеседовании важно не только знать ответ, но и уметь думать вслух.