Как пройти собеседование по JavaScript: топ вопросов продвинутого уровня
Собеседование на позицию Senior JavaScript-разработчика или Node.js инженера — это не просто проверка синтаксиса. Интервьюеры хотят увидеть глубокое понимание языка: как работает Event Loop, что такое контекст this на самом деле, и как избежать "адской" вложенности при работе с асинхронностью. В этой статье мы разберём самые частые и коварные вопросы, которые задают на продвинутом уровне. Готовьтесь не просто читать ответы, а разбираться в механике.
1. Event Loop, стек вызовов и микротаски (Microtasks vs Macrotasks)
Это, пожалуй, самый популярный блок на собеседованиях по Node.js. Вопрос звучит так: "Что выведет этот код и в каком порядке?".
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
process.nextTick(() => console.log('4'));
console.log('5');
Правильный ответ: 1, 5, 4, 3, 2.
Разбор:
- Синхронный код (1, 5) выполняется первым.
- process.nextTick() — это микротаска самого высокого приоритета. Она выполняется сразу после текущей операции, но ДО любых других микротасок (Promise) и макротасок (setTimeout).
- Promise.then() — тоже микротаска, но её приоритет ниже, чем у
nextTick. - setTimeout(cb, 0) — макротаска. Она выполняется только после того, как стек вызовов и все микротаски (все
nextTickиPromise) будут полностью очищены.
Как ответить правильно: Объясните, что Node.js имеет фазы Event Loop. Микротаски (process.nextTick и Promise) обрабатываются между фазами. Макротаски (setTimeout, setInterval, I/O) — внутри фаз. nextTick — это нестандартная, но очень важная очередь, которая может заблокировать Event Loop, если использовать её рекурсивно.
2. Контекст this и его "потеря"
Вопрос: "Как работает this в JavaScript? Приведите пример, где this теряется, и как это исправить".
Продвинутый уровень подразумевает, что вы знаете 4 правила:
- Новая привязка (new binding) — при вызове функции через
new,thisуказывает на новый созданный объект. - Явная привязка —
call,apply,bind. - Неявная привязка — когда функция вызывается как метод объекта (obj.method()).
- По умолчанию — глобальный объект (
globalв Node.js,windowв браузере) илиundefinedв строгом режиме.
Пример потери контекста:
const user = {
name: 'Alice',
greet() {
console.log(`Hello, ${this.name}`);
}
};
const greetFn = user.greet;
greetFn(); // Hello, undefined
Почему? Мы присвоили метод переменной. Теперь greetFn() вызывается без контекста объекта (user). this привязывается к глобальному объекту (или undefined в strict mode).
Как исправить:
- Использовать
bind:const greetFn = user.greet.bind(user); - Использовать стрелочную функцию (она не имеет своего
this, а берёт его из внешнего лексического окружения). - Вызывать через
call/apply:greetFn.call(user).
Бонусный вопрос: "А что будет, если user.greet передать в setTimeout?" — Тот же эффект потери. Исправляется через bind или стрелочную функцию в колбэке.
3. Замыкания (Closures) и их практическое применение
Вопрос: "Объясните, что такое замыкание. Зачем оно нужно в реальной разработке на Node.js?"
Определение: Замыкание — это функция, которая "запоминает" свою лексическую область видимости (переменные внешней функции), даже когда эта внешняя функция уже завершила свою работу.
Классический пример:
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
Переменная count не уничтожается после завершения createCounter, потому что внутренняя функция (замыкание) всё ещё держит на неё ссылку.
Продвинутое применение в Node.js:
- Фабрики функций: Создание middleware для Express с заранее заданными параметрами (например, проверка прав доступа).
- Мемоизация (кэширование результатов): Функция, которая запоминает результаты предыдущих вызовов для ускорения работы.
- Приватные переменные: Создание "приватных" свойств в объектах до появления классов ES6 (и даже сейчас, для сокрытия внутреннего состояния).
Ловушка на собеседовании: Цикл с var и замыканиями.
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Выведет: 3, 3, 3
Решение: заменить var на let (создаёт новую область видимости на каждой итерации) или использовать IIFE (Immediately Invoked Function Expression).
4. Async/Await и обработка ошибок: глубже, чем просто try/catch
Вопрос: "В чём разница между return await promise и return promise