Как пройти собеседование по JavaScript: топ вопросов продвинутого уровня
Собеседование на позицию Senior или Middle JavaScript-разработчика — это не просто проверка синтаксиса. Интервьюеры хотят увидеть глубокое понимание механизмов работы языка, особенно в среде Node.js. В этой статье мы разберём ключевые темы, которые часто вызывают трудности, и дадим примеры кода, которые помогут вам уверенно ответить на самые каверзные вопросы.
1. Замыкания (Closures) и лексическое окружение
Вопрос о замыканиях — классика. Но на продвинутом уровне важно не просто дать определение, а показать, как замыкания работают с let, const и в циклах.
Вопрос: Что выведет следующий код и почему?
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Вывод: 3, 3, 3
Проблема в том, что var имеет функциональную, а не блочную область видимости. К моменту вызова колбэков цикл уже завершился, и i равно 3. Решение — использовать let (создаёт новую привязку на каждой итерации) или обернуть вызов в замыкание:
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(() => console.log(j), 100);
})(i);
}
// Вывод: 0, 1, 2
В Node.js замыкания активно используются для создания приватных переменных и фабрик функций. Например, при написании middleware:
function createLogger(prefix) {
return function(message) {
console.log(`[${prefix}] ${message}`);
};
}
const infoLog = createLogger('INFO');
infoLog('Сервер запущен'); // [INFO] Сервер запущен
2. Event Loop и микротаски (Microtasks)
Понимание цикла событий — один из главных критериев оценки. Интервьюеры любят давать задачи на порядок вывода асинхронного кода.
Вопрос: В каком порядке выполнятся console.log?
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
Разбор:
console.log('1')иconsole.log('5')— синхронные, выполняются первыми.process.nextTick— это микротаска с самым высоким приоритетом. Она выполняется сразу после завершения текущей фазы.Promise.then— тоже микротаска, но с чуть более низким приоритетом, чемnextTick.setTimeout(fn, 0)— макротаска, выполняется в следующем тике Event Loop.
Для Node.js важно помнить про process.nextTick и setImmediate. Разница в том, что nextTick выполняется до любых вводов-выводов, а setImmediate — после.
3. Промисы и асинхронные итераторы (Async/Await)
Продвинутый уровень подразумевает умение работать с цепочками промисов, обработкой ошибок и параллельным выполнением.
Вопрос: Как обработать несколько асинхронных запросов, чтобы выполнить их параллельно, но дождаться всех результатов?
const fetchData = (id) => {
return new Promise((resolve) => {
setTimeout(() => resolve(`Данные для ID: ${id}`), id * 100);
});
};
async function getAllData() {
const ids = [1, 2, 3];
const promises = ids.map(id => fetchData(id));
const results = await Promise.all(promises);
console.log(results); // ['Данные для ID: 1', 'Данные для ID: 2', 'Данные для ID: 3']
}
getAllData();
Важный нюанс: Promise.all отклоняет весь промис при первой же ошибке. Если нужно получить все результаты (даже с ошибками), используйте Promise.allSettled. Это частая тема на собеседованиях в контексте Node.js микросервисов.
async function safeGetAll() {
const results = await Promise.allSettled([
fetchData(1),
fetchData(-1), // вызовет ошибку
]);
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log(result.value);
} else {
console.error('Ошибка:', result.reason);
}
});
}
4. Прототипное наследование и this
Вопросы про this и прототипы — это «лакмусовая бумажка» для понимания основ JS. В Node.js это особенно актуально при работе с классами и библиотеками.
Вопрос: В чём разница между function и стрелочной функцией в контексте this?
- Обычная функция имеет собственный
this, который зависит от контекста вызова (глобальный объект, объект, класс и т.д.). - Стрелочная функция не имеет своего
this; она наследует его из внешнего лексического окружения.
Пример из реального кода Node.js (обработчик события):
class Server {
constructor() {
this.name = 'MyServer';
// Проблема: this теряется
// setInterval(function() { console.log(this.name); }, 1000); // undefined
// Решение 1: стрелочная функция
setInterval(() => console.log(this.name), 1000); // MyServer
}
}
Также стоит знать, как работает цепочка прототипов. В Node.js это полезно для понимания работы Buffer или EventEmitter.
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.on('event', () => console.log('Событие сработало!'));
myEmitter.emit('event');
Здесь MyEmitter наследует методы on и emit через прототипную цепочку от EventEmitter