Junior JavaScript
Экспресс-шпаргалка 20/20
varимеет функциональную область видимости и hoisting сundefined.letиconstимеют блочную область видимости и TDZ.constнельзя переназначить.==сравнивает с приведением типов,===сравнивает без приведения. В прод-коде почти всегда используют===.undefinedобычно означает "значение не задано",null— "значение задано как пустое намеренно".- JavaScript всегда копирует и передаёт значения. Для объекта таким значением является ссылка, поэтому две переменные могут указывать на один объект.
- Hoisting — поднятие объявлений вверх области видимости. TDZ — период до инициализации
let/const, когда доступ к переменной запрещен. thisопределяется местом вызова функции, а не местом ее объявления.- Замыкание — функция, которая сохраняет доступ к переменным внешней функции после ее завершения.
- Если свойства нет у объекта, движок ищет его в прототипе, затем в прототипе прототипа и так далее до
null. - Event Loop координирует выполнение call stack и очередей задач, чтобы однопоточный JS мог обрабатывать асинхронные операции.
async/await— синтаксис поверх Promise, который делает асинхронный код более читаемым.- Microtasks выполняются сразу после текущего синхронного кода и до следующей macrotask.
mapпреобразует каждый элемент,filterотбирает элементы по условию,reduceсводит массив к одному значению.- Debounce откладывает вызов до паузы в событиях, throttle ограничивает частоту вызова фиксированным интервалом.
- Shallow copy копирует только первый уровень, deep copy — все вложенные уровни.
...в выражении — spread (раскрывает), в параметрах/деструктуризации — rest (собирает остаток).- Деструктуризация позволяет извлекать значения из объектов и массивов в переменные.
- Иммутабельность делает изменения состояния предсказуемыми, упрощает отладку и помогает оптимизациям рендера.
- Cookie отправляются на сервер с запросом,
localStorageживет между сессиями,sessionStorageочищается при закрытии вкладки. - CORS — механизм браузера, который регулирует междоменные запросы через специальные HTTP-заголовки.
- Обычный
scriptблокирует парсинг HTML.asyncзагружается параллельно и выполняется сразу после загрузки.deferзагружается параллельно и выполняется после парсинга DOM в порядке подключения.
Практический мост: вопросы 1-5
Эти пять вопросов закрывают первый блок урока 1 JavaScript: scope, сравнения, отсутствие значения, ссылки и TDZ. Идите по порядку: прочитать урок, ответить карточку, закрепить кодом, проверить себя на edge-case.
| Шаг | Навык | Ответить | Практика |
|---|---|---|---|
| 1 | Scope, reassignment, TDZ | q-1, q-5 | Песочница: "Две суммы": решите через const seen = new Map() и let i, затем объясните, почему const не замораживает Map. |
| 2 | Strict equality и coercion | q-2 | Свободная практика: напишите isFilled(value) и заранее выпишите результат для 0, '0', false, null, undefined, ''. |
| 3 | Контракт null/undefined | q-3 | Свободная практика: нормализуйте API-пользователя, где avatarUrl может отсутствовать, быть null или быть пустой строкой. |
| 4 | Ссылки и мутация | q-4 | Песочница: "Перенос нулей в конец": верните новый массив и докажите, что входной массив не изменился. |
| 5 | Предсказание выполнения | q-5 | Свободная практика: перед запуском предскажите вывод и ошибки для 5 коротких фрагментов с var, let, const и function declaration. |
Кластер считается закрытым, когда вы можете без подсказок предсказать вывод 10 сниппетов, назвать production-баг для каждого вопроса и объяснить, какой edge-case проверяет ваш пример.
1. В чем разница между var, let и const?
Теги: javascript, variables, scope, hoisting
Сложность: Junior
Короткий ответ
var имеет функциональную область видимости и hoisting с undefined. let и const имеют блочную область видимости и TDZ. const нельзя переназначить.
Что сказать на интервью (30-60 секунд)
varживет в функции, аletиconstживут в блоке:if,for,{}. Поэтомуvarможет неожиданно "утечь" за пределы блока.varподнимается и до строки объявления даетundefined;letиconstтоже попадают в область видимости, но до инициализации находятся в TDZ и даютReferenceError.constзапрещает переназначить переменную, но не делает объект илиMapimmutable. Поэтомуconst user = {}можно мутировать черезuser.name = ....- Практическое правило: по умолчанию
const, если нужно переназначение -let,varв новом коде почти всегда избегаем.
Мини-пример
var a = 1;
let b = 2;
const c = { value: 3 };
c.value = 4;
Углубление (2-3 минуты)
- Production-сценарий: обработчики в цикле с
varмогут получить одно и то же финальное значение индекса, потому что переменная общая для всей функции. - Контрпример:
const config = {}; config.theme = 'dark'работает, ноconfig = {}падает. Это показывает разницу между binding и содержимым объекта. - Практика: в задаче "Две суммы" используйте
const seen = new Map()иlet i. Объясните, почемуseen.set(...)разрешен, аseen = new Map()нет.
Типичные ошибки
- Говорят, что
constделает объект неизменяемым. Это неверно безObject.freezeили иммутабельного подхода. - Говорят, что
letиconst"не hoist-ятся". Корректнее: они hoist-ятся, но недоступны в TDZ. - Ожидают от
varблочную область видимости внутриifилиfor.
Follow-up вопросы
- Что выведут три
setTimeoutвнутриforсvar i, и что изменится сlet i? - Почему
const arr = []не мешает выполнитьarr.push(1)? - Как бы вы защитили объект от мутаций, если это действительно требуется?
Что повторить
- Урок 1: Hoisting, scope, TDZ.
- Мини-код: цикл с
var/let, доступ к переменной до объявления, мутация объекта вconst. - Песочница: "Две суммы" - проговорите выбор
constиletв своем решении.
Связанные модули и карта
2. В чем разница между == и ===?
Теги: javascript, equality, type-coercion, operators
Сложность: Junior
Короткий ответ
== сравнивает с приведением типов, === сравнивает без приведения. В прод-коде почти всегда используют ===.
Что сказать на интервью (30-60 секунд)
===сравнивает без приведения типов: тип и значение должны совпасть.==сначала пытается привести типы по правилам Abstract Equality.- В рабочем коде почти всегда выбираю
===, потому что0 == false,'' == falseи'0' == 0часто маскируют ошибку данных. - Осознанное исключение встречается редко: например,
value == null, если специально хотим поймать иnull, иundefined. - Проверяю таблицей edge-cases, а не одним примером:
0,'0',false,'',null,undefined,NaN.
Мини-пример
0 == false; // true
0 === false; // false
Углубление (2-3 минуты)
- Production-сценарий: форма присылает
userIdстрокой'0', а код сравнивает с числом0. С==баг может пройти незаметно, с===станет видно, что контракт данных смешан. - Для проверок наличия значения отделяйте "пусто" от "ложно":
if (value)сломается на0, а явная проверкаvalue !== null && value !== undefinedсохранит смысл. - Практика: в свободной практике напишите
isFilled(value)и отдельно решите, какие значения должны считаться заполненными в вашем сценарии.
Типичные ошибки
- Говорят "всегда только
===" и не могут объяснить редкий осознанный кейсvalue == null. - Смешивают truthy/falsy с equality:
if (value)- это не то же самое, чтоvalue === true. - Не проверяют
NaN: дажеNaN === NaNдаетfalse, для него нуженNumber.isNaN.
Follow-up вопросы
- Почему
0 == falsetrue, а0 === falsefalse? - Чем опасна проверка
if (count)для счетчика, который может быть равен0? - Когда вы бы допустили
value == null, и как прокомментировали бы это в коде?
Что повторить
- Урок 1: Типы и сравнения без магии.
- Таблицу edge-cases:
0,'0',false,'',null,undefined,NaN,[]. - Свободная практика: реализуйте
isFilled(value)и объясните каждое решение до запуска.
Связанные модули и карта
3. Чем null отличается от undefined?
Теги: javascript, null, undefined, types
Сложность: Junior
Короткий ответ
undefined обычно означает "значение не задано", null — "значение задано как пустое намеренно".
Что сказать на интервью (30-60 секунд)
undefinedчаще означает, что значение не передали, свойство отсутствует или переменную еще не присвоили.nullобычно означает осознанное пустое значение: поле есть, но сейчас оно пустое.- В API и UI это разные состояния. Например,
avatarUrl: undefinedможет значить "не обновлять поле", аavatarUrl: null- "очистить аватар". - Проверяю не только значение, но и наличие свойства:
'avatarUrl' in userотличается отuser.avatarUrl === undefined.
Мини-пример
let a;
const obj = { v: null };
console.log(a, obj.missing, obj.v);
Углубление (2-3 минуты)
- Production-сценарий: PATCH формы профиля. Если поле отсутствует - оставляем старое значение; если пришел
null- явно очищаем. ??помогает отличатьnull/undefinedот валидных falsy-значений вроде0и'', но контракт все равно нужно проговорить.- Практика: в свободной практике напишите
normalizeUser(input), который различает отсутствующийavatarUrl,avatarUrl: nullиavatarUrl: ''.
Типичные ошибки
- Используют
if (!value)и случайно смешиваютnull,undefined,0,falseи пустую строку. - Не различают "свойства нет" и "свойство есть, но значение
undefined". - Меняют контракт API без миграции: клиенты начинают по-разному трактовать пустое значение.
Follow-up вопросы
- Чем отличаются
obj.missing,obj.value = undefinedиobj.value = null? - Когда лучше вернуть пустой массив вместо
null? - Какой тест докажет, что PATCH не стирает поле случайно?
Что повторить
- Урок 1: Типы и сравнения без магии.
??, optional chaining, операторin, явные проверкиvalue === nullиvalue === undefined.- Свободная практика: нормализация API-объекта с отсутствующим,
nullи пустым значением.
Связанные модули и карта
4. Что такое примитивные и ссылочные типы?
Теги: javascript, primitives, references, mutation
Сложность: Junior
Короткий ответ
JavaScript всегда копирует и передаёт значения. У примитива копируется само значение, а у объекта - значение-ссылка, поэтому две переменные могут указывать на один объект.
Что сказать на интервью (30-60 секунд)
- Примитивы (
string,number,boolean,null,undefined,symbol,bigint) передаются и сравниваются как значения. - Для объекта передаётся значение-ссылка. Функция получает копию этой ссылки, поэтому
const b = a; b.x = 2меняет один объект, доступный и черезa, и черезb; переназначениеbпри этом не переназначитa. - В UI это важно для state: скрытая мутация массива или объекта может не дать ожидаемого обновления и усложнит отладку.
- Проверяю примером, где функция получает объект, мутирует его и после вызова видно, изменился ли оригинал.
Мини-пример
const a = { x: 1 };
const b = a;
b.x = 2;
console.log(a.x); // 2
Углубление (2-3 минуты)
- Production-сценарий: компонент мутирует
items.push(newItem)вместо создания нового массива. Данные изменились, но сравнение ссылок не показывает новое значение как отдельное состояние. - Для shallow copy через
{ ...obj }или[...arr]копируется только первый уровень. Вложенные объекты остаются общими ссылками. - Практика: решите "Перенос нулей в конец" так, чтобы функция вернула новый массив и не изменила входной
nums.
Типичные ошибки
- Думают, что
constзащищает объект от внутренней мутации. - Считают shallow copy полноценной deep copy и случайно делят вложенные объекты между состояниями.
- Сравнивают объекты через
===и ожидают сравнение по содержимому.
Follow-up вопросы
- Почему
{ x: 1 } === { x: 1 }даетfalse? - Что изменится после
const copy = [...items], если внутриitemsлежат объекты? - Какой тест докажет, что функция не мутирует входной массив?
Что повторить
- Урок 1: reference equality vs value equality.
- Мини-код:
const b = a, shallow copy, вложенный объект, сравнение двух одинаковых литералов. - Песочница: "Перенос нулей в конец" - добавьте ручную проверку, что исходный массив остался прежним.
Связанные модули и карта
5. Что такое hoisting и TDZ?
Теги: javascript, hoisting, tdz, execution-context
Сложность: Junior
Короткий ответ
Hoisting — поднятие объявлений вверх области видимости. TDZ — период до инициализации let/const, когда доступ к переменной запрещен.
Что сказать на интервью (30-60 секунд)
- Hoisting - это этап подготовки области видимости перед выполнением кода: движок заранее регистрирует объявления.
varпри этом инициализируется какundefined, поэтому чтение до строки объявления не падает, но дает неготовое значение.letиconstтоже известны движку заранее, но находятся в TDZ до фактической строки объявления. Чтение в TDZ даетReferenceError.- Function declaration можно вызвать до объявления, а function expression в
constподчиняется TDZ как обычная переменная.
Мини-пример
console.log(x); // undefined
var x = 1;
// console.log(y); // ReferenceError
let y = 2;
Углубление (2-3 минуты)
- Production-сценарий: код читает конфиг или handler до инициализации. С
varбаг может превратиться в тихийundefined, сlet/constошибка проявится сразу. - TDZ полезна тем, что не дает использовать переменную до явной инициализации, особенно в длинных блоках и условных ветках.
- Практика: в свободной практике перед запуском подпишите рядом с каждым сниппетом: "вывод", "ReferenceError" или "работает".
Типичные ошибки
- Объясняют hoisting как физическое "перемещение строк кода". Надежнее говорить про создание bindings до выполнения.
- Смешивают
varhoisting и function declaration hoisting. - Забывают, что
typeof valueв TDZ дляlet/constтоже может броситьReferenceError.
Follow-up вопросы
- Что произойдет при
console.log(a); var a = 1и почему? - Почему
console.log(b); let b = 1падает, хотя объявление есть ниже? - Чем отличается
foo()доfunction foo() {}отbar()доconst bar = () => {}?
Что повторить
- Урок 1: Hoisting, scope, TDZ.
- Мини-код:
varдо объявления,letдо объявления, function declaration до объявления, function expression вconst. - Свободная практика: соберите 5 сниппетов, сначала предскажите результат письменно, потом запускайте.
Связанные модули и карта
Практический мост: вопросы 6-10
Этот блок закрывает вторую часть урока 1 JavaScript и урок 2 про async/runtime. Цель - не выучить термины, а научиться предсказывать выполнение кода и объяснять реальные баги.
| Шаг | Навык | Ответить | Практика |
|---|---|---|---|
| 1 | this в точке вызова | q-6 | Свободная практика: сравните method call, detached function, bind и arrow callback на одном объекте user. |
| 2 | Closure и сохранение окружения | q-7 | Свободная практика: реализуйте createCounter() и once(fn), затем докажите, что состояние не глобальное. |
| 3 | Prototype lookup | q-8 | Свободная практика: создайте объект через Object.create, проверьте in, hasOwnProperty и Object.keys. |
| 4 | Event Loop | q-9 | Свободная практика: заранее выпишите порядок логов для sync, Promise.then, queueMicrotask, setTimeout. |
| 5 | Promise и async/await | q-10 | Свободная практика: напишите fetchWithTimeout(url, ms) с AbortController и обработкой ошибки отмены. |
Кластер считается закрытым, когда вы можете до запуска предсказать вывод async-сниппета, объяснить потерю this, показать closure без глобального состояния и назвать один production-баг для каждого вопроса.
6. Как работает this в JavaScript?
Теги: javascript, this, bind, call-apply
Сложность: Junior
Короткий ответ
this определяется местом вызова функции, а не местом ее объявления. У arrow function своего this нет: она берет его из внешней области.
Что сказать на интервью (30-60 секунд)
- В обычной функции
thisзависит от call-site:user.hi()иconst fn = user.hi; fn()дают разный контекст. - В strict mode detached function получает
this === undefined; без strict mode в браузере можно случайно попасть в global object. bind,callиapplyявно задаютthis, а arrow function не подходит для методов объекта, которым нужен динамический receiver.- Production-риск: callback передали без привязки, handler потерял объект и начал читать
undefinedвместо нужного state.
Мини-пример
const user = { name: 'Ann', hi() { return this.name; } };
const fn = user.hi;
fn(); // undefined в strict mode
user.hi(); // "Ann"
Углубление (2-3 минуты)
- Production-сценарий: метод класса или объекта передали как
onClick={user.hi}. При вызове handler уже не вызывается какuser.hi(), поэтому контекст потерян. - Контрпример:
user.hi.bind(user)фиксирует контекст, но arrow method внутри объекта не получаетthisот объекта. - Практика: в свободной практике сделайте один объект
userи четыре вызова: method call, detached call,bind, arrow callback. Перед запуском подпишите ожидаемый результат.
Типичные ошибки
- Говорят, что
thisопределяется местом объявления функции. - Используют arrow function как метод объекта и ждут, что
thisстанет самим объектом. - Объясняют
bind/call/applyкак одно и то же, не называя разницу в моменте вызова и формате аргументов.
Follow-up вопросы
- Что изменится между
button.addEventListener('click', user.hi)иbutton.addEventListener('click', user.hi.bind(user))? - Почему
const fn = user.hi; fn()теряетthis? - Когда arrow callback помогает сохранить внешний
this, а когда ломает метод объекта?
Что повторить
- Урок 1:
thisи prototype chain. - Мини-код: method call, detached function,
bind,call,apply, arrow callback. - Свободная практика: объясните каждый вывод до запуска и затем сравните с фактическим результатом.
Связанные модули и карта
7. Что такое замыкание?
Теги: javascript, closures, lexical-scope, memory
Сложность: Junior
Короткий ответ
Замыкание - это функция, которая сохраняет доступ к переменным внешней функции после завершения этой внешней функции.
Что сказать на интервью (30-60 секунд)
- Closure возникает, когда внутренняя функция использует переменные из lexical scope внешней функции.
- Переменная хранится не как копия значения на момент создания, а как доступ к окружению, поэтому состояние может меняться между вызовами.
- Типичные применения: счетчики, memoization, debounce/throttle, приватное состояние без глобальных переменных.
- Production-риск: случайно удержали большую структуру в closure и получили утечку памяти или stale state.
Мини-пример
function counter() {
let n = 0;
return () => ++n;
}
const next = counter();
next(); // 1
next(); // 2
Углубление (2-3 минуты)
- Production-сценарий: debounce хранит
timerIdв closure, чтобы следующий вызов мог отменить предыдущий таймер. - Контрпример: если положить
nв global scope, несколько счетчиков начнут делить одно состояние; с closure у каждого counter свое окружение. - Практика: в свободной практике реализуйте
createCounter()иonce(fn). Проверьте два отдельных counter и повторный вызовonce.
Типичные ошибки
- Говорят, что closure - это просто "функция внутри функции", хотя важен доступ к внешним переменным.
- Не различают lexical scope и
this: closure сохраняет переменные, но не "чинит" потерянныйthis. - Забывают про memory risk: closure может удерживать объект дольше, чем нужно.
Follow-up вопросы
- Почему два вызова
counter()создают независимое состояние? - Как closure помогает реализовать debounce?
- Какой пример покажет, что closure может хранить stale value?
Что повторить
- Урок 1: Closure.
- Мини-код:
createCounter,once(fn), debounce сtimerIdв closure. - Свободная практика: докажите, что состояние не лежит в global scope.
Связанные модули и карта
8. Что такое prototype chain?
Теги: javascript, prototypes, inheritance, objects
Сложность: Junior
Короткий ответ
Prototype chain - это цепочка поиска свойства: сначала own property объекта, затем его prototype, затем prototype прототипа и так до null.
Что сказать на интервью (30-60 секунд)
- Если свойства нет на самом объекте, JS ищет его выше по
[[Prototype]]. - Prototype chain влияет на чтение свойства, но не означает, что свойство скопировано в объект.
inпроверяет own и inherited свойства, аObject.hasOwnилиhasOwnPropertyпроверяют только own property.- Production-риск: код принимает inherited property за реальные данные объекта и ошибается при сериализации, валидации или переборе ключей.
Мини-пример
const animal = { sound: '...' };
const dog = Object.create(animal);
dog.name = 'Rex';
console.log(dog.sound); // ...
console.log(Object.keys(dog)); // ['name']
Углубление (2-3 минуты)
- Production-сценарий: валидация payload проверяет
'role' in user, ноroleпришел из prototype, а не из данных. Надежнее проверять own property. - Контрпример:
dog.soundчитается, ноObject.keys(dog)не показываетsound, потому что это inherited property. - Практика: в свободной практике создайте
dogчерезObject.create(animal)и сравнитеin,Object.hasOwn(dog, 'sound'),Object.keys(dog).
Типичные ошибки
- Думают, что prototype chain копирует свойства в объект.
- Путают prototype chain с class inheritance и не могут объяснить lookup на простом объекте.
- Используют
for...inбез проверки own property и случайно обрабатывают inherited keys.
Follow-up вопросы
- Почему
dog.soundесть, аObject.keys(dog)не показываетsound? - Чем отличается
'sound' in dogотObject.hasOwn(dog, 'sound')? - Что может пойти не так при переборе объекта через
for...in?
Что повторить
- Урок 1: Prototype chain.
- Мини-код:
Object.create,in,Object.hasOwn,Object.keys,for...in. - Свободная практика: объясните, где свойство лежит физически и как это проверить.
Связанные модули и карта
9. Что такое Event Loop?
Теги: javascript, event-loop, async, tasks
Сложность: Junior
Короткий ответ
Event Loop управляет порядком выполнения: сначала синхронный call stack, затем microtasks, затем следующая macrotask.
Что сказать на интервью (30-60 секунд)
- JS выполняет один синхронный стек за раз; async API только планируют продолжение работы.
- После завершения текущего sync-кода движок очищает microtask queue (
Promise.then,queueMicrotask), и только потом берет macrotask (setTimeout, события, I/O). - Это объясняет порядок логов и помогает разбирать гонки, зависшие UI-обновления и неожиданный порядок state updates.
- Production-риск: код ожидает, что
setTimeout(..., 0)выполнится "сразу", но перед ним могут пройти все microtasks.
Мини-пример
console.log(1);
setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
console.log(4); // 1, 4, 3, 2
Углубление (2-3 минуты)
- Production-сценарий: после клика вы ставите
setTimeoutдля UI-эффекта, но Promise chain обновляет state раньше, поэтому визуальный порядок отличается от ожидаемого. - Контрпример: если добавить несколько
Promise.then, они выполнятся до первогоsetTimeout, даже когда timeout равен0. - Практика: в свободной практике соберите сниппет с sync log,
queueMicrotask,Promise.thenиsetTimeout. Сначала выпишите порядок, потом запускайте.
Типичные ошибки
- Думают, что
setTimeout(fn, 0)выполняется доPromise.then. - Называют все async-операции "очередью", не различая microtask и macrotask.
- Не учитывают, что длинный sync-код блокирует обработку любых очередей.
Follow-up вопросы
- Почему
Promise.thenвыполнится раньшеsetTimeout(..., 0)? - Что будет, если синхронный цикл выполняется 2 секунды?
- Как Event Loop помогает объяснить race condition в UI?
Что повторить
- Урок 2: Event Loop.
- Мини-код: sync logs,
queueMicrotask,Promise.then,setTimeout. - Свободная практика: предскажите порядок 5 сниппетов и отметьте, где microtask queue очищается полностью.
Связанные модули и карта
10. Разница между Promise и async/await?
Теги: javascript, promises, async-await, error-handling
Сложность: Junior
Короткий ответ
async/await - это синтаксис поверх Promise: он делает async-код похожим на последовательный, но результат async функции все равно Promise.
Что сказать на интервью (30-60 секунд)
- Promise описывает будущий результат:
pending, затемfulfilledилиrejected. asyncфункция всегда возвращает Promise, даже если внутри возвращается обычное значение.awaitприостанавливает выполнение текущей async-функции до settle Promise, но не блокирует весь JS-поток.- Ошибки с
awaitобычно ловят черезtry/catch; в.then()цепочке нужен.catch()или второй callback. - Production-риск: забыли
awaitили обработку rejection, UI показывает успех до завершения запроса или теряет ошибку.
Мини-пример
async function load() {
try {
const res = await fetch('/api');
return await res.json();
} catch (error) {
return { error: error.message };
}
}
Углубление (2-3 минуты)
- Production-сценарий: форма вызывает
saveUser()безawait, показывает "сохранено", а запрос падает позже. Пользователь видит ложный успех. - Контрпример:
return fetch(url)иreturn await fetch(url)похожи, ноtry/catchвнутри async-функции поймает ошибку только если Promise awaited. - Практика: в свободной практике реализуйте
fetchWithTimeout(url, ms)черезAbortController; отдельно обработайтеAbortError, чтобы отмена не выглядела как обычный сбой.
Типичные ошибки
- Думают, что
awaitблокирует весь Event Loop. - Забывают
returnв.then()цепочке и получают Promise сundefined. - Смешивают реальные ошибки запроса, отмену и timeout в один generic catch без понятного поведения для UI.
Follow-up вопросы
- Что вернет
async function f() { return 1; }? - Почему забытый
await saveUser()может сломать UX формы? - Как отличить отмененный запрос от настоящей ошибки сервера?
Что повторить
- Урок 2: Promise combinators и cancellation.
- Мини-код:
asyncreturn value,try/catch,.then().catch(), забытыйawait. - Свободная практика: реализуйте
fetchWithTimeoutи опишите поведение для success, timeout и network error.
Связанные модули и карта
Практический мост: вопросы 11-15
Цель блока - научиться предсказывать порядок выполнения, выбирать подходящий метод коллекций и объяснять, где "удобный синтаксис" ломает состояние.
| Шаг | Навык | Ответить | Практика |
|---|---|---|---|
| 1 | Очереди microtask/macrotask | q-11 | Свободная практика: предскажите порядок логов для console.log, Promise.then, queueMicrotask и setTimeout(0), затем проверьте запуском. |
| 2 | Трансформации коллекций | q-12 | Песочница: "Слияние двух отсортированных массивов": решите без мутации входных массивов и объясните, почему здесь не нужен reduce. |
| 3 | Контроль частоты событий | q-13 | Свободная практика: реализуйте debounce(fn, 300) и throttle(fn, 300), затем сравните логи при пяти быстрых вызовах. |
| 4 | Глубина копирования | q-14 | Песочница: "Перенос нулей в конец": верните новый массив и отдельно покажите пример, где { ...state } сохраняет ссылку на вложенный объект. |
| 5 | Spread/rest в данных и функциях | q-15 | Свободная практика: соберите sum(...nums), mergeConfig(defaults, override) и пример, где порядок spread меняет итоговое значение. |
Готовность: вы можете до запуска предсказать вывод async-примера, выбрать map/filter/reduce по форме результата, назвать баг от shallow copy и объяснить разницу между spread и rest на двух коротких фрагментах.
11. Разница microtask и macrotask?
Теги: javascript, microtask, macrotask, event-loop
Сложность: Junior
Короткий ответ
Microtask выполняется после текущего синхронного кода и до следующей macrotask; macrotask - это следующий крупный шаг очереди событий, например callback setTimeout.
Что сказать на интервью (30-60 секунд)
- Сначала выполняется весь sync-код текущего стека.
- Затем runtime дренирует очередь microtasks:
Promise.then,queueMicrotask, продолжениеawait. - Только после этого берется следующая macrotask:
setTimeout, пользовательское событие, часть I/O. - Production-риск: код в
Promise.thenможет изменить state раньше, чем callback таймера, поэтому неверное ожидание порядка дает мигающий UI или stale-state. - Проверка: выписываю ожидаемый порядок логов, запускаю мини-фрагмент и отдельно проверяю случай с несколькими microtasks подряд.
Мини-пример
console.log('sync-1');
setTimeout(() => console.log('macro'), 0);
queueMicrotask(() => console.log('micro-1'));
Promise.resolve().then(() => console.log('micro-2'));
console.log('sync-2');
// sync-1, sync-2, micro-1, micro-2, macro
Углубление (2-3 минуты)
- Production-сценарий: после клика вы ставите optimistic state в
Promise.then, а fallback черезsetTimeout; microtask сработает раньше таймера даже при0ms. - Контрпример: длинная цепочка microtasks может задержать переход к следующей macrotask и ухудшить отзывчивость.
- Практика: в Песочнице напишите 3 фрагмента с
await,Promise.then,queueMicrotaskиsetTimeout(0), перед запуском запишите ожидаемый порядок.
Типичные ошибки
- Считают, что
setTimeout(..., 0)выполнится сразу после текущей строки. - Не учитывают, что все накопленные microtasks выполняются до следующей macrotask.
- Объясняют порядок словом "асинхронно", но не называют конкретную очередь.
Follow-up вопросы
- Что выведет пример, если внутри
Promise.thenдобавить еще одинPromise.then? - Почему бесконечная цепочка microtasks может заморозить интерфейс?
- Чем
await Promise.resolve()похож наPromise.thenс точки зрения очереди?
Что повторить
- Урок 2: Event Loop.
- Таблицу: sync stack -> microtasks -> next macrotask.
- Free-practice prompt: "predict logs" для
console.log,queueMicrotask,Promise.then,await,setTimeout(0).
Связанные модули и карта
12. Для чего map, filter, reduce?
Теги: javascript, arrays, map-filter-reduce, functional-style
Сложность: Junior
Короткий ответ
map возвращает новый массив той же длины, filter возвращает подмножество элементов, reduce сводит массив к аккумулятору любого типа.
Что сказать на интервью (30-60 секунд)
- Выбираю метод по форме результата: столько же элементов -
map, меньше или равно -filter, одно значение/объект/словарь -reduce. - Эти методы не обязаны мутировать исходный массив, поэтому хорошо подходят для подготовки данных к рендеру.
- Production-риск:
mapради side effect или сложныйreduceухудшает читаемость и прячет баги в трансформации. - Проверка: прогоняю пустой массив, один элемент и несколько элементов с ожидаемым результатом.
Мини-пример
const users = [
{ name: 'Ann', active: true, score: 10 },
{ name: 'Bob', active: false, score: 7 },
];
const names = users.map(user => user.name); // ['Ann', 'Bob']
const active = users.filter(user => user.active); // [{ name: 'Ann', ... }]
const totalScore = users.reduce((sum, user) => sum + user.score, 0); // 17
Углубление (2-3 минуты)
- Production-сценарий: API вернул список заказов, а UI должен показать только оплаченные заказы, их названия и общую сумму.
- Контрпример: если
reduceпревращается в 20 строк условий, лучше разбить трансформацию на именованные шаги или обычный цикл. - Практика: в Песочнице: "Слияние двух отсортированных массивов" сохраните входные массивы неизменными и объясните, почему выбран цикл, а не
map.
Типичные ошибки
- Используют
mapбез возвращаемого значения и получают массивundefined. - Мутируют объекты внутри
map, ожидая "чистую" трансформацию. - Выбирают
reduceради краткости, хотя результат проще читается черезfilter+map.
Follow-up вопросы
- Какой метод выберете, если нужно получить объект
{ id: user }, и почему? - Что вернет
map, если callback ничего не возвращает? - Какие тесты нужны для пустого массива и массива из одного элемента?
Что повторить
- Урок 3: структуры данных и функциональные подходы.
- Разницу "форма входа" и "форма результата": same-length, subset, accumulator.
- Free-practice prompt: преобразуйте список заказов в
{ count, revenue, paidIds }без мутации входа.
Связанные модули и карта
13. Что такое debounce и throttle?
Теги: javascript, debounce, throttle, performance
Сложность: Junior
Короткий ответ
Debounce вызывает функцию после паузы в событиях, throttle разрешает вызывать функцию не чаще заданного интервала.
Что сказать на интервью (30-60 секунд)
- Debounce подходит для поиска по вводу: ждем, пока пользователь перестал печатать.
- Throttle подходит для scroll/resize/drag: обрабатываем поток событий с ограниченной частотой.
- Production-риск: без контроля частоты UI шлет лишние запросы, перегружает main thread и получает stale responses.
- Проверка: имитирую серию быстрых вызовов и смотрю timestamps, сколько раз реально вызвался handler.
Мини-пример
const debouncedSearch = debounce(searchUsers, 300);
const throttledScroll = throttle(saveScrollPosition, 100);
input.addEventListener('input', debouncedSearch);
window.addEventListener('scroll', throttledScroll);
Углубление (2-3 минуты)
- Production-сценарий: autocomplete без debounce отправляет запрос на каждый символ, а старый медленный ответ может перетереть свежий результат.
- Контрпример: для drag-preview debounce даст ощущение задержки, поэтому чаще нужен throttle или
requestAnimationFrame. - Практика: в Песочнице реализуйте
debounceиthrottle, вызовите каждую функцию 5 раз подряд и объясните разницу по логам времени.
Типичные ошибки
- Выбирают debounce для непрерывного drag/scroll, где пользователю нужна регулярная обратная связь.
- Не очищают таймер debounce и получают вызов после размонтирования компонента.
- Не учитывают leading/trailing-вызов и получают пропущенное первое или последнее действие.
Follow-up вопросы
- Что изменится, если debounce должен вызываться на leading edge?
- Как предотвратить stale response в поиске, кроме debounce?
- Почему throttle не гарантирует, что обработчик будет вызван с последним значением?
Что повторить
- Урок 2: async runtime и stale-response.
- Разницу
debounce: "после паузы" иthrottle: "не чаще интервала". - Free-practice prompt: реализуйте оба helper-а и напишите 3 проверки для bursts, single call и delayed call.
Связанные модули и карта
14. Разница shallow copy и deep copy?
Теги: javascript, shallow-copy, deep-copy, structuredclone
Сложность: Junior
Короткий ответ
Shallow copy копирует только верхний уровень структуры, а вложенные объекты остаются теми же ссылками; deep copy копирует вложенность тоже.
Что сказать на интервью (30-60 секунд)
{ ...obj },[...arr],Object.assignдают shallow copy.- Если внутри есть объект или массив, новая внешняя обертка все еще указывает на старую вложенность.
- Production-риск: в React/Redux-like state можно "обновить" внешний объект, но случайно мутировать вложенный state и сломать мемоизацию.
- Проверка: меняю вложенное поле в копии и смотрю, изменился ли оригинал.
Мини-пример
const state = { user: { name: 'Ann' }, theme: 'dark' };
const nextState = { ...state };
nextState.user.name = 'Bob';
console.log(state.user.name); // 'Bob'
Углубление (2-3 минуты)
- Production-сценарий: компонент получает
settings.notifications.email, shallow copy меняетsettings, но вложенный объект остается общим. - Контрпример: для плоского объекта
{ page: 1, sort: 'name' }shallow copy достаточно и проще deep clone. - Практика: решите Песочницу: "Перенос нулей в конец" так, чтобы не мутировать входной массив, затем отдельно покажите shallow-copy баг на вложенном объекте.
Типичные ошибки
- Думают, что spread делает объект полностью независимым.
- Используют
JSON.parse(JSON.stringify(obj))без учетаDate,undefined, функций и циклических ссылок. - Делают deep copy всего state без причины и получают лишнюю стоимость вместо точечного immutable update.
Follow-up вопросы
- Что изменится, если вложенное поле будет массивом объектов?
- Когда
structuredCloneподойдет, а когда нет? - Как написать проверку, которая докажет, что оригинал не мутируется?
Что повторить
- Урок 3: иммутабельность shallow vs deep.
- Разницу между новой внешней ссылкой и новыми вложенными ссылками.
- Free-practice prompt: обновите
state.user.profile.cityбез мутации исходногоstate.
Связанные модули и карта
15. Что делают spread и rest операторы?
Теги: javascript, spread-rest, destructuring, functions
Сложность: Junior
Короткий ответ
Один синтаксис ... имеет две роли: spread раскрывает значения в месте использования, rest собирает оставшиеся значения в массив или объект.
Что сказать на интервью (30-60 секунд)
- Spread используется в вызовах, массивах и объектах:
fn(...args),[...items],{ ...defaults }. - Rest используется в параметрах и деструктуризации:
function sum(...nums),const { id, ...rest } = user. - Production-риск: порядок spread в объекте решает, какие поля будут перезаписаны, а копирование остается shallow.
- Проверка: показываю пример, где
{ ...defaults, ...override }и{ ...override, ...defaults }дают разные значения.
Мини-пример
const defaults = { pageSize: 20, theme: 'light' };
const override = { theme: 'dark' };
const config = { ...defaults, ...override }; // theme: 'dark'
function sum(...nums) {
return nums.reduce((total, n) => total + n, 0);
}
Углубление (2-3 минуты)
- Production-сценарий: конфиг виджета собирается из defaults, env и user options; неверный порядок spread перетирает пользовательскую настройку.
- Контрпример:
const copy = [...items]копирует массив, но не копирует глубоко объекты внутри него. - Практика: в Песочнице реализуйте
mergeConfig(defaults, env, user)иpickPublicUser({ password, ...publicUser }), затем объясните порядок перезаписи.
Типичные ошибки
- Называют любой
...spread, хотя в параметрах функции это rest. - Забывают, что более поздний spread в объекте перезаписывает более ранние поля.
- Ожидают deep copy от
[...items]или{ ...object }.
Follow-up вопросы
- Что вернет
{ a: 1, ...{ a: 2 }, a: 3 }? - Чем
function fn(...args)отличается отfn(...args)? - Как spread работает с массивом объектов и почему это связано с shallow copy?
Что повторить
- Урок 3: структуры данных и иммутабельность.
- Две роли
...: раскрыть значения и собрать остаток. - Free-practice prompt: напишите
sum,mergeConfigи destructuring-rest для удаления приватного поля.
Связанные модули и карта
Практический мост: вопросы 16-20
Цель блока - закрыть финальные browser/runtime темы: безопасное чтение данных, обновление state, хранение в браузере, CORS и загрузка скриптов.
| Шаг | Навык | Ответить | Практика |
|---|---|---|---|
| 1 | Деструктуризация и safe access | q-16 | Песочница: "Получение значения по пути": верните undefined, если путь упирается в null/undefined, и объясните связь с optional chaining. |
| 2 | Иммутабельное обновление state | q-17 | Свободная практика: обновите state.user.profile.city без мутации исходного state и проверьте ссылки prev === next. |
| 3 | Browser storage и риск токенов | q-18 | Свободная практика: составьте таблицу data -> storage -> срок жизни -> XSS/CSRF risk -> mitigation. |
| 4 | CORS и preflight | q-19 | Свободная практика: напишите CORS-политику для dev/stage/prod и пример preflight-ответа с Vary: Origin. |
| 5 | Загрузка скриптов | q-20 | Свободная практика: нарисуйте timeline для script, async, defer и объясните, где ломается порядок зависимых скриптов. |
Готовность: вы можете безопасно читать вложенные данные, обновлять nested state без мутации, выбрать storage под риск, объяснить CORS как серверную политику и предсказать порядок выполнения скриптов.
16. Что такое деструктуризация?
Теги: javascript, destructuring, defaults, optional-chaining
Сложность: Junior
Короткий ответ
Деструктуризация извлекает значения из массивов и объектов в переменные; для вложенных данных нужно учитывать undefined, defaults и optional chaining.
Что сказать на интервью (30-60 секунд)
- Для объекта важны имена свойств, для массива - позиции.
- Default value срабатывает только при
undefined, но не приnull. - Вложенная деструктуризация бросит ошибку, если промежуточный объект отсутствует.
- Production-риск: компонент упадет на
Cannot read properties of undefined, если API вернул неполный профиль. - Проверка: прогоняю полный объект, отсутствующее поле,
nullи пустой массив.
Мини-пример
const user = { name: 'Sam', meta: undefined };
const { name, role = 'guest' } = user;
const city = user.meta?.city ?? 'unknown';
Углубление (2-3 минуты)
- Production-сценарий: API иногда не присылает
profile.address, а UI должен показать fallback вместо падения страницы. - Контрпример:
const { meta: { city } } = userупадет, еслиmetaравенundefined. - Практика: решите Песочницу: "Получение значения по пути" и объясните, почему проверка
value == nullиногда удобна для остановки наnullиundefined.
Типичные ошибки
- Думают, что default value сработает для
null. - Деструктурируют глубокий объект без проверки промежуточных уровней.
- Путают alias
const { id: userId } = userс созданием поляid.
Follow-up вопросы
- Что вернет
const { x = 1 } = { x: null }? - Как безопасно достать
items[0].title, еслиitemsможет отсутствовать? - Чем
const { id: userId } = userотличается отconst { id } = user?
Что повторить
- Урок 3: структуры данных и функциональные подходы.
- Связку destructuring defaults, optional chaining и nullish coalescing.
- Free-practice prompt: распакуйте nested API response без падения на отсутствующих полях.
Связанные модули и карта
17. Зачем нужна иммутабельность в фронтенде?
Теги: javascript, immutability, state, frontend
Сложность: Junior
Короткий ответ
Иммутабельность помогает обновлять state через новые ссылки, чтобы UI и мемоизация могли надежно понять, что данные изменились.
Что сказать на интервью (30-60 секунд)
- Во фронтенде часто сравнивают ссылки: если ссылка не изменилась, обновление могут не заметить.
- Иммутабельное обновление не меняет старый объект, а создает новый объект для измененной ветки.
- Это упрощает undo, debugging, memo/cache и повторяемые тесты.
- Production-риск: мутировали nested state, компонент не перерендерился или memoized selector вернул старый результат.
- Проверка: сравниваю ссылки старого и нового state и убеждаюсь, что исходный объект не изменился.
Мини-пример
const nextState = {
...state,
user: {
...state.user,
profile: { ...state.user.profile, city: 'Kazan' },
},
};
Углубление (2-3 минуты)
- Production-сценарий: пользователь меняет город в профиле, но UI с
memoне обновляется, потому что вложенный объект мутировали на месте. - Контрпример: для локальной временной переменной внутри функции мутация массива может быть нормальной, если она не выходит наружу как shared state.
- Практика: в Песочнице обновите
state.user.profile.city, затем проверьтеstate !== nextState,state.user !== nextState.userи отсутствие мутации старогоstate.
Типичные ошибки
- Делают shallow copy верхнего уровня, но мутируют вложенный объект.
- Считают, что иммутабельность означает "никогда не использовать push", хотя вопрос в ownership и видимости мутации.
- Создают deep clone всего state без необходимости и получают лишнюю стоимость.
Follow-up вопросы
- Какие ссылки должны измениться при обновлении
state.user.profile.city? - Когда локальная мутация безопасна?
- Какой тест докажет, что старый state остался неизменным?
Что повторить
- Урок 3: иммутабельность shallow vs deep.
- Разницу между mutation, immutable update и ownership данных.
- Free-practice prompt: обновите элемент массива объектов по
idбез мутации входного массива.
Связанные модули и карта
18. Разница между cookie, localStorage и sessionStorage?
Теги: web, cookies, storage, security
Сложность: Junior
Короткий ответ
Cookie привязаны к HTTP-запросам и атрибутам безопасности, localStorage хранится между сессиями и доступен JS, sessionStorage доступен JS и живет в пределах вкладки.
Что сказать на интервью (30-60 секунд)
- Cookie автоматически отправляются на подходящий домен/path;
HttpOnlycookie нельзя прочитать из JavaScript. localStorageпереживает закрытие вкладки и браузера, но любой XSS может прочитать его содержимое.sessionStorageочищается при закрытии вкладки, но XSS-риск остается.- Production-риск: refresh token в
localStorageпревращает XSS в кражу долгоживущей сессии. - Проверка решения: называю срок жизни данных, кто может читать, отправляется ли на сервер автоматически и какой основной риск.
Мини-пример
localStorage.setItem('theme', 'dark');
sessionStorage.setItem('checkoutStep', 'payment');
document.cookie = 'lang=ru; Path=/; SameSite=Lax';
Углубление (2-3 минуты)
- Production-сценарий: тему интерфейса можно хранить в
localStorage, но refresh token лучше не класть в JS-доступное хранилище. - Контрпример: cookie без
HttpOnlyтоже доступна JS, поэтому слово "cookie" само по себе не означает безопасность. - Практика: в Песочнице составьте таблицу для
theme,draft form,access token,refresh token: storage, срок жизни, XSS/CSRF-риск, mitigation.
Типичные ошибки
- Говорят "cookie безопаснее" без упоминания
HttpOnly,Secure,SameSite. - Хранят секреты в
localStorage, потому что "так проще восстановить сессию". - Путают XSS-риск чтения JS-хранилища и CSRF-риск автоматической отправки cookie.
Follow-up вопросы
- Почему refresh token в
localStorageопасен при XSS? - Что меняет
SameSite=LaxилиSameSite=Strict? - Где лучше хранить UI theme и почему это не то же самое, что хранение токена?
Что повторить
- Безопасность и хранение: storage options и риски.
- Карта подготовки: безопасность и хранение.
- Free-practice prompt: опишите storage-решение для login flow и отметьте, какие данные нельзя логировать.
Связанные модули и карта
- Обучение: безопасность и хранение
- Карта подготовки: безопасность и хранение
- Карта подготовки: JavaScript
19. Что такое CORS?
Теги: web, cors, http, browser-security
Сложность: Junior
Короткий ответ
CORS - это браузерный механизм, который разрешает или запрещает JavaScript читать cross-origin ответ на основе HTTP-заголовков сервера.
Что сказать на интервью (30-60 секунд)
- CORS не "отправляет запросы", а ограничивает чтение ответа браузерным кодом.
- Политика задается сервером через
Access-Control-Allow-*, frontend не может надежно "починить CORS" настройкойfetch. - Для нестандартных методов/headers браузер сначала делает preflight
OPTIONS. - Production-риск:
Access-Control-Allow-Origin: *в prod с чувствительными API маскирует плохую модель доступа и ломает credentials-сценарии. - Проверка: смотрю Network preflight, origin, allowed methods/headers, credentials и
Vary: Origin.
Мини-пример
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET,POST,OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Vary: Origin
Углубление (2-3 минуты)
- Production-сценарий: dev работает на
localhost, stage на отдельном домене, prod наapp.example.com; для каждого окружения нужен свой whitelist origin. - Контрпример: CORS не заменяет AuthZ; если endpoint отдает чужие данные после валидного запроса, это серверная ошибка доступа.
- Практика: в Песочнице напишите CORS-политику для
dev/stage/prodи пример preflight для запроса сAuthorization.
Типичные ошибки
- Добавляют
mode: 'no-cors'и думают, что получили нормальный JSON-ответ. - Ставят
Access-Control-Allow-Origin: *вместе с credentials-сценарием. - Путают CORS, CSRF, authentication и authorization.
Follow-up вопросы
- Почему CORS настраивается на сервере?
- Когда браузер делает preflight
OPTIONS? - Зачем нужен
Vary: Originпри динамическом allowlist?
Что повторить
- Web и сеть: CORS и API-контракты.
- Безопасность и хранение: CSRF, CORS и защита API.
- Free-practice prompt: разберите CORS-ошибку в DevTools по
Request Headers,Response Headersи preflight.
Связанные модули и карта
20. В чем разница между script, async и defer?
Теги: web, script-loading, async-defer, performance
Сложность: Junior
Короткий ответ
Обычный script блокирует HTML parsing, async загружается параллельно и выполняется сразу после загрузки без гарантии порядка, defer загружается параллельно и выполняется после парсинга DOM в порядке подключения.
Что сказать на интервью (30-60 секунд)
- Без атрибутов браузер останавливает парсинг HTML, загружает и выполняет script, затем продолжает парсинг.
asyncхорош для независимых скриптов: analytics, виджеты, которые не зависят от порядка.deferхорош для основного app bundle: DOM уже распарсен, порядок нескольких deferred scripts сохраняется.- Production-риск: зависимый script с
asyncможет выполниться раньше библиотеки и упасть в медленной сети. - Проверка: рисую timeline
parse -> download -> execute, тестирую с network throttling и смотрю порядок логов.
Мини-пример
<script src="/vendor.js" defer></script>
<script src="/app.js" defer></script>
<script src="/analytics.js" async></script>
Углубление (2-3 минуты)
- Production-сценарий:
vendor.jsиapp.jsзависят от порядка, поэтому их безопаснее грузить сdefer, а независимую аналитику - сasync. - Контрпример: если inline script сразу читает DOM-элемент ниже по HTML, обычный blocking script может выполниться до появления элемента.
- Практика: в Песочнице запишите expected timeline для трех scripts: blocking,
async,defer, затем объясните, почемуasyncне подходит для зависимой пары.
Типичные ошибки
- Считают, что
asyncвсегда быстрее и безопаснее. - Забывают, что несколько
asyncscripts не сохраняют порядок выполнения. - Не проверяют slow network, где порядок загрузки отличается от локального быстрого сценария.
Follow-up вопросы
- Что выбрать для основного bundle, который зависит от DOM?
- Почему analytics обычно можно грузить через
async? - Что сломается, если
app.jsсasyncвыполнится раньшеvendor.js?
Что повторить
- Карта подготовки: JavaScript, Browser runtime.
- Модель
parse HTML -> download script -> execute script -> DOMContentLoaded. - Free-practice prompt: нарисуйте timeline для
script,async,deferпри быстрой и медленной сети.
Связанные модули и карта
Куда дальше
- Вернитесь в модуль: JavaScript.
- Сверьтесь с картой темы: JavaScript.
- Закройте смежный модуль: Безопасность и хранение.
- Продолжайте по маршруту: Junior трек.
- Закрепите один ответ на практике в Песочнице.