Как пройти собеседование по JavaScript: продвинутый уровень

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Как пройти собеседование по 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

Блоги

Рекомендаций по книгам