Перейти к основному содержимому

JavaScript

Для чего модуль

Собрать не «набор фактов про язык», а рабочую инженерную модель JavaScript: как код реально выполняется, где чаще всего рождаются баги, и как это объяснять на собеседовании и в команде.

Результат после прохождения

  1. Вы объясняете поведение JS-кода через execution model, а не через заученные исключения.
  2. Вы уверенно разбираете async-сценарии: порядок выполнения, отмену, гонки, деградацию.
  3. Вы выбираете структуру данных и стиль преобразований с учетом читаемости и стоимости операций.
  4. Вы умеете довести «плавающий баг» до воспроизводимости и root cause.

Термины и аббревиатуры

ТерминКоротко
TDZВременная мертвая зона
ClosureФункция + внешнее окружение
MicrotaskПриоритетная очередь задач
Event LoopМодель выполнения задач
PrototypeМеханизм наследования

Фокус по грейдам

  1. Junior: понимать базовые механики и объяснять их простыми примерами.
  2. Middle: применять тему в продуктовых сценариях с учетом рисков и ограничений.
  3. Senior: управлять архитектурными trade-offs, метриками и эволюцией решения.

Как работать с модулем

  1. Каждый урок проходите в связке: теория -> код в консоли/песочнице -> разбор реального кейса.
  2. Для каждой темы фиксируйте два артефакта: краткое объяснение на 40-60 секунд и мини-кейс «где это ломалось/могло сломаться».
  3. После урока закрывайте минимум 3 вопроса из банка по этой теме.

Программа модуля

Урок 1. База языка

Цель: закрыть фундаментальные вопросы, на которых сыпятся даже сильные кандидаты.

Каркас мышления

Перед разбором любого куска JS задавайте 4 вопроса:

  1. Какие здесь типы и как они сравниваются?
  2. В какой области видимости живут переменные?
  3. Какое значение this в точке вызова?
  4. Как будет найдено свойство: own property или по prototype chain?

Типы и сравнения без магии

В JS важно разделять:

  1. Primitive values (string, number, bigint, boolean, null, undefined, symbol).
  2. Objects (включая функции, массивы, даты, map/set).
  3. Reference equality vs value equality.
console.log(0 === false); // false
console.log(0 == false); // true (coercion)

console.log([] === []); // false (разные ссылки)
const a = { x: 1 };
const b = a;
console.log(a === b); // true

Практическое правило: в продуктовой логике избегайте неявных сравнений (==) кроме явно понятных кейсов.

Hoisting, scope, TDZ

var hoist-ится с undefined, let/const hoist-ятся в TDZ.

function demo() {
console.log(a); // undefined
// console.log(b); // ReferenceError
var a = 1;
let b = 2;
}

Прод-следствие: баги из-за случайного var часто выглядят как «иногда undefined в неожиданный момент».

this и prototype chain

this определяется местом вызова, а не местом объявления функции (кроме arrow-function).

const user = {
name: 'Ira',
regular() { return this.name; },
arrow: () => this?.name,
};

console.log(user.regular()); // "Ira"
console.log(user.arrow()); // undefined в strict mode окружении модуля

Prototype chain отвечает за lookup свойств, а не за копирование: это важно при полиморфизме и при дебаге «почему свойство есть в объекте, но не в own keys».

Где ломается в проде

  1. Случайная мутация объекта из shared state.
  2. Неверный this в callback/handler.
  3. Логика на ==, которая проходит тесты и ломается на edge-case данных.

Мини-задача (обязательная)

Подготовьте 5 объяснений по 40-60 секунд: var/let/const, ==/===, null/undefined, this, prototype chain. Для каждого объяснения добавьте один реальный баг-кейс (или правдоподобный сценарий).

Что спросит интервьюер: почему const не делает объект полностью immutable и где это ломает state в UI.

Критерий готовности по уроку: вы можете предсказать поведение 10 коротких фрагментов кода и объяснить каждое поведение через модель языка.

Урок 2. Асинхронность и runtime

Цель: уметь предсказуемо объяснять порядок выполнения и защищаться от async-багов.

Event Loop: минимальная рабочая модель

  1. Сначала выполняется sync-код.
  2. Потом дренируются microtasks (Promise.then, queueMicrotask).
  3. Потом берется следующая macrotask (setTimeout, I/O callbacks).
console.log('A');
setTimeout(() => console.log('B'), 0);
Promise.resolve().then(() => console.log('C'));
console.log('D');
// A, D, C, B

Promise combinators и выбор по задаче

  1. Promise.all - «все или ошибка», быстрый fail.
  2. Promise.allSettled - «получить статус каждого».
  3. Promise.race - «первый завершившийся».
  4. Promise.any - «первый успешный», иначе AggregateError.

Cancellation и stale-response

Одна из самых частых причин «UI показывает старые данные»: старый запрос завершается позже нового и перетирает state.

let currentController;

async function loadUsers(query) {
currentController?.abort();
currentController = new AbortController();

const res = await fetch(`/api/users?q=${encodeURIComponent(query)}`, {
signal: currentController.signal,
});
return res.json();
}

Где ломается в проде

  1. «Проглоченные» ошибки в async-цепочке.
  2. Retry без лимита попыток.
  3. Неправильная обработка abort, когда отмена считается «настоящей ошибкой».

Мини-задача (обязательная)

Разберите 5 фрагментов с setTimeout, Promise, async/await: предскажите порядок логов и объясните, почему именно так. Отдельно реализуйте fetch с timeout + cancel.

Что спросит интервьюер: как вы предотвращаете гонки запросов при быстром переключении фильтров в UI.

Критерий готовности по уроку: вы можете объяснить порядок выполнения кода и показать рабочую защиту от race-condition в UI/сервисе.

Урок 3. Структуры данных и функциональные подходы

Цель: писать предсказуемый и поддерживаемый код для коллекций и трансформаций.

Выбор структуры данных

  1. Array - упорядоченные коллекции, удобно для трансформаций и рендера.
  2. Map - быстрый lookup по ключу и сохранение insertion order.
  3. Set - уникальность значений без ручной дедупликации.
  4. Plain object - хорошо для сериализации, но не всегда лучший выбор для динамического словаря.

Чистые функции и контролируемые side effects

Чистая функция: при одинаковых входах всегда одинаковый выход и нет побочных эффектов. Это резко упрощает тестирование и рефакторинг.

function aggregateOrders(orders) {
return orders.reduce(
(acc, order) => {
const total = order.items.reduce((s, i) => s + i.price * i.qty, 0);
acc.count += 1;
acc.revenue += total;
return acc;
},
{ count: 0, revenue: 0 }
);
}

Иммутабельность: shallow vs deep

{ ...obj } копирует только первый уровень. Если внутри вложенный объект/массив, ссылка сохраняется.

Где это ломается:

  1. Redux-like state обновляется «как будто immutable», но вложенность мутируется.
  2. memo/shouldComponentUpdate не срабатывают корректно.

Readability vs cleverness

reduce полезен, когда реально есть аккумуляция. Если логика читается хуже, выбирайте map + filter и промежуточные имена.

Мини-задача (обязательная)

Реализуйте преобразование списка заказов в агрегированную статистику без мутации входа. Добавьте тест на edge-cases: пустой список, заказ без items, невалидная цена.

Что спросит интервьюер: почему иногда reduce хуже читается, чем комбинация map + filter.

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

Урок 4. Прод-кейсы и дебаг

Цель: перейти от «знаю теорию» к «могу разобрать инцидент».

Runbook: stale data после фильтрации

Пошаговый шаблон:

  1. Зафиксировать сценарий воспроизведения (какие клики, какой интервал, какие фильтры).
  2. Снять network timeline и убедиться, что ответы приходят в другом порядке.
  3. Проверить логику обновления state: есть ли guard по requestId/AbortController.
  4. Проверить, нет ли двойного запроса из-за эффекта/ре-рендера.
  5. Добавить временное логирование requestId -> response -> state update.
  6. Зафиксировать fix и добавить regression test.

Инструменты, которые должны быть «под рукой»

  1. Breakpoints + conditional breakpoints.
  2. Network tab и сортировка по start/end time.
  3. Performance markers (performance.mark/measure) для ключевых операций.
  4. Structured logs в консоли для сложных async-цепочек.

Что отличает инженерный дебаг от хаотичного

  1. У вас есть гипотеза перед каждым действием.
  2. Каждый шаг уменьшает множество возможных причин.
  3. После фикса есть защита от повтора (тест, guard, мониторинг).

Мини-задача (обязательная)

Соберите runbook для бага: «иногда показываются устаревшие данные после фильтрации». Добавьте: признаки бага, шаги проверки, root cause, fix, проверку фикса.

Что спросит интервьюер: как вы доказываете, что проблема именно в async flow, а не в API.

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

Практика

  1. Решите 12 задач: 8 из Junior JavaScript и 4 из Middle JavaScript и React.
  2. Напишите три утилиты: debounce, throttle, fetchWithTimeoutAndAbort.
  3. Реализуйте агрегатор заказов без мутаций + тесты на edge-cases.
  4. Подготовьте один debug-case: «stale data после фильтрации» с полным runbook.
  5. Отработайте спорные вопросы формулировок в Playbook ответов.
  6. Разберите один async-баг (гонка/устаревшие данные) в Песочнице и зафиксируйте фикс.

Связь с треками и вопросами

  1. Треки: Junior трек, Middle трек, Senior трек.
  2. Вопросы: Junior JavaScript, Middle JavaScript и React, Senior Frontend.
  3. Повторение: 3-5 вопросов без подсказок -> сверка с модулем -> повтор через 24 часа.

Критерий готовности

Вы даете уверенное объяснение любой темы JS на 1-2 минуты: модель -> пример -> where-it-breaks -> как чинить/предотвращать.

Артефакты после модуля

  1. Набор из 12 коротких, структурных ответов на частые JS-вопросы.
  2. Конспект async-паттернов с рисками и правилами использования.
  3. Мини-библиотека утилит (debounce, throttle, timeout/cancel).
  4. Один полноценно оформленный runbook на реальный или учебный баг.

Куда дальше

  1. React
  2. TypeScript
  3. JavaScript