SprintCode.pro

Подготовка к алгоритмическим задачам

Super

Вопросы по JavaScript на собеседовании frontend-разработчика

14 мин чтения
собеседование
javascript
frontend

Коротко

Вопросы по JavaScript на собеседовании группируются в шесть блоков. Ниже — каждый с разбором и объяснением, что именно проверяют.

БлокЧто проверяют
Типы и приведениепонимание основ, внимательность
Замыкания и область видимостиключевая тема, спрашивают почти всегда
this и контекстсамая частая ловушка
Прототипы и наследованиепонимание устройства языка
Event loop и асинхронностьглавная тема для middle+
Промисы и async/awaitпрактика

Блок 1. Типы и приведение

В чём разница между == и ===

=== сравнивает без приведения типов, == приводит операнды к общему типу.

1 == '1' // true — строка приводится к числу 1 === '1' // false null == undefined // true — специальное правило null === undefined // false NaN == NaN // false — NaN не равен ничему, включая себя

Проверка на NaN: Number.isNaN(x) или x !== x.

Что проверяют: знаете ли вы правила приведения. Практический ответ: используйте === всегда, кроме проверки на null/undefined одним сравнением.

Чем отличаются null и undefined

undefined — переменная объявлена, но значение не присвоено. null — намеренно присвоенное «пустое» значение.

let a; console.log(a); // undefined console.log(typeof a); // 'undefined' let b = null; console.log(typeof b); // 'object' — историческая ошибка языка

typeof null === 'object' — известный баг, оставленный ради обратной совместимости. Про него любят спрашивать.

Что выведет и почему

console.log(0.1 + 0.2 === 0.3); // false console.log(0.1 + 0.2); // 0.30000000000000004

Числа хранятся в формате двойной точности, и 0.1 в двоичном виде — бесконечная дробь. Сравнивать нужно с погрешностью: Math.abs(a - b) < Number.EPSILON.

Пройди собеседование в топ-компанию
Платформа для подготовки

Решай алгоритмические задачи как профи

✓ Популярные алгоритмы✓ Разбор решений✓ AI помощь
Начать сейчас
Программист за работой

Блок 2. Замыкания

Что такое замыкание

Функция вместе с областью видимости, в которой она была создана. Внутренняя функция сохраняет доступ к переменным внешней даже после её завершения.

function counter() { let count = 0; // живёт в замыкании return function () { return ++count; }; } const next = counter(); next(); // 1 next(); // 2

Зачем нужны: приватные переменные, каррирование, мемоизация, обработчики событий с сохранённым состоянием.

Классическая задача про цикл

for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); } // 3, 3, 3

var имеет функциональную область видимости — все три колбэка ссылаются на одну переменную, которая к моменту выполнения равна 3.

Исправление:

for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); } // 0, 1, 2 — let создаёт новую привязку на каждой итерации

Это вопрос-классика. Ответ должен включать объяснение почему, а не только исправление.

Блок 3. Контекст this

Самая частая ловушка на собеседованиях frontend.

Правила определения this при вызове:

ВызовЧему равен this
Обычная функцияundefined в strict, window иначе
Метод объектасам объект
Стрелочная функцияthis из места объявления
call, apply, bindчто передали
Конструктор с newновый объект
const obj = { name: 'объект', regular() { console.log(this.name); // 'объект' }, arrow: () => { console.log(this.name); // undefined — this внешний }, nested() { setTimeout(function () { console.log(this.name); // undefined — потерян контекст }, 0); setTimeout(() => { console.log(this.name); // 'объект' — стрелка сохранила }, 0); }, };

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

Потеря контекста

const user = { name: 'Аня', greet() { return `Привет, ${this.name}`; }, }; const fn = user.greet; fn(); // ошибка: this не определён const bound = user.greet.bind(user); bound(); // работает

Блок 4. Прототипы

Как работает наследование

Каждый объект имеет ссылку на прототип. При обращении к свойству движок ищет его в самом объекте, затем в прототипе, затем в прототипе прототипа — до null. Это цепочка прототипов.

const animal = { speak() { return 'звук'; }, }; const dog = Object.create(animal); dog.name = 'Рекс'; dog.speak(); // 'звук' — взято из прототипа Object.getPrototypeOf(dog) === animal; // true

Классы в ES6 — синтаксический сахар над прототипами, а не отдельный механизм.

class Animal { speak() { return 'звук'; } } // эквивалентно записи функции-конструктора с методом в prototype

Что проверяют: понимаете ли вы, что под классами лежат прототипы. Ответ «в JS классическое наследование» — красный флаг.

Блок 5. Event loop

Главная тема для позиций middle и выше.

Что выведет

console.log('1'); setTimeout(() => console.log('2'), 0); Promise.resolve().then(() => console.log('3')); console.log('4'); // 1, 4, 3, 2

Разбор:

  1. Синхронный код выполняется первым: 1, 4.
  2. Затем опустошается очередь микрозадач — промисы: 3.
  3. И только потом очередь макрозадач — таймеры: 2.

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

МикрозадачиМакрозадачи
Promise.thensetTimeout
queueMicrotasksetInterval
MutationObserverсобытия DOM
requestAnimationFrame

Почему setTimeout(fn, 0) не мгновенный

Он ставит задачу в очередь макрозадач, которая обрабатывается только после завершения синхронного кода и всех микрозадач. Минимальная задержка в браузерах — около 4 мс для вложенных вызовов.

Блок 6. Промисы и async/await

Разница между Promise.all и Promise.allSettled

// all — падает при первой ошибке await Promise.all([ok(), fail(), ok()]); // отклонится // allSettled — всегда дожидается всех const results = await Promise.allSettled([ok(), fail()]); // [{status: 'fulfilled', value}, {status: 'rejected', reason}]

Есть ещё Promise.race (первый завершившийся, любой) и Promise.any (первый успешный).

Частая ошибка: последовательность вместо параллели

// медленно: запросы идут по очереди for (const url of urls) { results.push(await fetch(url)); } // быстро: параллельно const results = await Promise.all(urls.map((u) => fetch(u)));

Это любимый вопрос на код-ревью в рамках интервью.

Обработка ошибок

try { const data = await fetchData(); } catch (error) { // ловит и синхронные ошибки, и отклонённые промисы } finally { // выполнится в любом случае }

Без try/catch отклонённый промис в async-функции даёт необработанное отклонение.

Как готовиться

Разберитесь в event loop. Это тема, которая отличает junior от middle. Задачи «что выведет» на промисах и таймерах дают почти всегда.

Практикуйте this. Напишите десяток примеров с разными способами вызова и предскажите результат до запуска.

Не заучивайте ответы. Спрашивают «почему», а не «что». Ответ «потому что так работает» не засчитывается.

Готовьте примеры из опыта. «Мы ловили утечку памяти из-за замыкания в обработчике» весит больше, чем определение замыкания.

Что запомнить

  • === вместо == везде, кроме проверки на null/undefined.
  • Замыкание — функция плюс её область видимости; классическая задача про var в цикле.
  • У стрелочных функций нет своего this — они берут его из места объявления.
  • Классы в JavaScript — сахар над прототипами.
  • Микрозадачи (промисы) выполняются раньше макрозадач (таймеров).
  • Параллельные запросы через Promise.all, а не await в цикле.