Задачи с кодом

Что выведет код: 10 задач JavaScript с собеседования и разбор

Обновлено 7 мин чтения
Содержание
  1. Коротко
  2. Как решать такие задачи вслух
  3. 1. var и let в цикле с setTimeout
  4. 2. Поднятие и временная мёртвая зона
  5. 3. this в методе, стрелке и оторванной функции
  6. 4. Микрозадачи и таймер
  7. 5. async/await и executor промиса
  8. 6. Микрозадача внутри таймера
  9. 7. Приведение типов
  10. 8. Замыкание счётчика
  11. 9. typeof
  12. 10. Объекты и массивы передаются по ссылке
  13. Как готовиться к вопросам «что выведет»
  14. Частые ошибки
  15. Чек-лист
  16. Вопросы из базы
  17. Как тренировать
  18. Частые вопросы
Коротко

Вопрос «что выведет код» звучит почти на каждом frontend-собеседовании: интервьюер показывает 5-15 строк и просит назвать вывод консоли по порядку. Так за пару минут проверяют, понимаете ли вы области видимости, this, event loop и приведение типов. Ниже 10 типичных задач, у каждой настоящий вывод Node.js и разбор, почему он такой.

Как решать такие задачи вслух

Главное правило: не угадывайте, а исполняйте код в голове построчно. Проговаривайте, что происходит на каждой строке. Если ошиблись в одном месте, интервьюер увидит, что ход мысли верный, и часто подскажет.

  1. Сначала найдите всё синхронное: обычные вызовы console.log идут первыми.
  2. Потом микрозадачи: then, catch, finally, queueMicrotask и продолжение после await.
  3. Потом таймеры: setTimeout и setInterval, в порядке постановки.
  4. Отдельно отметьте, где объявлена переменная и как вызвана функция: от этого зависят значение и this.
Все примеры ниже запущены как ES-модули в Node.js. В модуле строгий режим включён всегда, а this на верхнем уровне равен undefined. В обычном скрипте браузера часть ответов отличалась бы, и это хороший повод уточнить у интервьюера, где выполняется код.

1. var и let в цикле с setTimeout

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log('var', i), 0);
}
for (let j = 0; j < 3; j++) {
  setTimeout(() => console.log('let', j), 0);
}
Вывод
var 3
var 3
var 3
let 0
let 1
let 2

Почему так. У var одна переменная на всю функцию. Таймеры срабатывают после цикла, когда i уже равна 3, и все три колбэка видят одно и то же значение. let создаёт новую переменную на каждой итерации, и каждый колбэк замыкает свою. Классический ответ на продолжение «как починить с var»: обернуть тело в функцию и передать i аргументом. Потренировать: var в цикле и let в цикле.

2. Поднятие и временная мёртвая зона

console.log(a);
var a = 1;

console.log(hello());
function hello() {
  return 'привет';
}

try {
  console.log(b);
  let b = 2;
} catch (e) {
  console.log(e.name);
}
Вывод
undefined
привет
ReferenceError

Почему так. Объявление var поднимается в начало области видимости, а присваивание остаётся на месте. Поэтому a уже есть, но равна undefined. Объявление функции поднимается целиком, вместе с телом, и её можно вызвать до строки с function. let и const тоже поднимаются, но до строки объявления к ним нельзя обращаться: это временная мёртвая зона, и обращение бросает ReferenceError. Подробно в уроке про области видимости и замыкания.

3. this в методе, стрелке и оторванной функции

const user = {
  name: 'Аня',
  regular() {
    return this.name;
  },
  arrow: () => typeof this,
  later() {
    setTimeout(() => console.log('later:', this.name), 0);
  },
};

console.log(user.regular());
console.log(user.arrow());
const detached = user.regular;
try {
  console.log(detached());
} catch (e) {
  console.log(e.name);
}
user.later();
Вывод
Аня
undefined
TypeError
later: Аня

Почему так. У обычной функции this определяется в момент вызова: при вызове через точку это объект слева. У стрелки своего this нет, она берёт его из места, где создана. Здесь это верхний уровень модуля, где this равен undefined. Оторванный метод вызывается без объекта, в строгом режиме this внутри равен undefined, и чтение this.name бросает TypeError. Стрелка внутри setTimeout в методе later берёт this метода, поэтому имя на месте. Разбор правил в уроке про this и контекст.

4. Микрозадачи и таймер

console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
queueMicrotask(() => console.log('4'));
console.log('5');
Вывод
1
5
3
4
2

Почему так. Сначала весь синхронный код: 1 и 5. Затем очередь микрозадач выполняется целиком, в порядке постановки: 3 и 4. Только после этого браузер или Node берёт следующую макрозадачу, то есть таймер. setTimeout с нулём не означает «сразу», он означает «не раньше следующего круга». Подробно в уроке про event loop.

5. async/await и executor промиса

async function run() {
  console.log('B');
  await null;
  console.log('D');
}

console.log('A');
run();
new Promise((resolve) => {
  console.log('C');
  resolve();
}).then(() => console.log('E'));
setTimeout(() => console.log('F'), 0);
console.log('G');
Вывод
A
B
C
G
D
E
F

Почему так. Тело async-функции до первого await выполняется синхронно, поэтому B идёт сразу после A. Функция, переданная в new Promise, тоже выполняется синхронно: отсюда C. Всё, что после await, становится микрозадачей и ставится в очередь раньше, чем then второго промиса. Поэтому D, потом E. Таймер последним. Похожая задача: async/await и порядок.

6. Микрозадача внутри таймера

console.log('start');
setTimeout(() => console.log('timeout 1'), 0);
setTimeout(() => {
  console.log('timeout 2');
  Promise.resolve().then(() => console.log('micro in timeout 2'));
}, 0);
setTimeout(() => console.log('timeout 3'), 0);
Promise.resolve()
  .then(() => console.log('then 1'))
  .then(() => console.log('then 2'));
console.log('end');
Вывод
start
end
then 1
then 2
timeout 1
timeout 2
micro in timeout 2
timeout 3

Почему так. then 2 ставится в очередь, когда выполнился then 1, но очередь микрозадач разбирается до конца, поэтому оба then идут подряд до таймеров. После каждой макрозадачи снова разбирается вся очередь микрозадач. Поэтому микрозадача из второго таймера выполняется раньше третьего таймера. Тренировка: микрозадача между двумя таймерами.

7. Приведение типов

console.log('5' + 2);
console.log('5' - 2);
console.log([] + {});
console.log(JSON.stringify([] + []));
console.log(true + 1);
console.log(null == undefined);
console.log(null == 0);
console.log(NaN === NaN);
Вывод
52
3
[object Object]
""
2
true
false
false

Почему так. Плюс со строкой склеивает, поэтому 52. Минус работает только с числами, и строка превращается в число 3. Массив и объект при сложении приводятся к строкам: пустой массив даёт пустую строку, объект даёт [object Object]. true в арифметике равно 1. null нестрого равен только undefined и самому себе, с нулём он не равен. NaN не равен ничему, даже себе. Проверять его нужно через Number.isNaN. Правила собраны в уроке про приведение типов.

8. Замыкание счётчика

function makeCounter() {
  let count = 0;
  return () => ++count;
}

const a = makeCounter();
const b = makeCounter();
console.log(a(), a(), b());
Вывод
1 2 1

Почему так. Каждый вызов makeCounter создаёт новую переменную count, и возвращённая функция держит ссылку именно на неё. У a и b разные счётчики. Частое продолжение: «а если count объявить снаружи функции». Тогда счётчик станет общим и ответ будет 1 2 3. Похожая задача: общее замыкание.

9. typeof

console.log(typeof null);
console.log(typeof undefined);
console.log(typeof function () {});
console.log(typeof []);
console.log(Array.isArray([]));
console.log(typeof NaN);
Вывод
object
undefined
function
object
true
number

Почему так. typeof null возвращает object: это старая ошибка языка, которую оставили ради совместимости. Для функций typeof отдельно возвращает function, хотя функция тоже объект. Массив для typeof просто объект, поэтому массив проверяют через Array.isArray. NaN по типу число, хоть и означает «не число».

10. Объекты и массивы передаются по ссылке

const a = { n: 1 };
const b = a;
b.n = 2;
console.log(a.n);

const list = [1, 2];
function add(arr) {
  arr.push(3);
  arr = [0];
  return arr;
}
console.log(add(list), list);

console.log({ x: 1 } === { x: 1 });
Вывод
2
[ 0 ] [ 1, 2, 3 ]
false

Почему так. В переменной лежит ссылка на объект, а не сам объект. a и b указывают на один объект, поэтому изменение через b видно через a. Внутри add метод push меняет исходный массив, а присваивание arr = [0] только перенаправляет локальную переменную на новый массив, list это не трогает. Два одинаковых литерала это два разных объекта, сравнение по ссылке даёт false.

Как готовиться к вопросам «что выведет»

  1. Сначала пишите ответ сами, на бумаге или в заметке. Только потом запускайте код.
  2. Если ошиблись, найдите правило, которое нарушили, и запишите его одной фразой.
  3. Через несколько дней решите ту же задачу снова. Правило должно вспоминаться само.
  4. Решайте вслух. На собеседовании ценят объяснение «почему», а не только верные цифры.

Такие задачи часто идут блоком на техническом скрининге. Остальные темы junior собраны в разделе что должен знать junior frontend.

Частые ошибки

  • Угадывать вывод целиком, а не исполнять код построчно
  • Считать, что setTimeout с нулём выполнится раньше промиса
  • Забывать, что тело async-функции до первого await синхронное
  • Путать this стрелки и обычной функции
  • Не уточнить, где выполняется код: модуль, скрипт браузера или Node

Чек-лист

  • Объясняете разницу var, let и const с примером цикла
  • Знаете порядок: синхронный код, микрозадачи, таймеры
  • Можете сказать, чему равен this в четырёх случаях вызова
  • Помните основные правила приведения при плюсе, минусе и нестрогом сравнении
  • Понимаете, почему объекты сравниваются по ссылке

Вопросы из базы Техпрогона

Частые вопросы по теме с ответом и разбором. Откройте любой и попробуйте ответить сами.

Как тренировать в Техпрогоне

  • В каталоге JavaScript больше сотни вопросов типа «что выведет код» с разбором каждой строки.
  • Билеты с зачётом по JavaScript: 20 вопросов, зачёт при двух ошибках и меньше, ошибки показываются с разбором сразу после ответа.
  • Ошибки попадают в повторения: вопрос вернётся через несколько дней, пока правило не закрепится. Раздел «Мои ошибки» и марафон по треку доступны в тарифе «Рывок».
  • Перед каждой темой можно прочитать короткий урок: все уроки бесплатны.

Частые вопросы

Зачем на собеседовании спрашивают, что выведет код?
Это быстрый способ проверить понимание языка. За пару минут видно, знаете ли вы event loop, области видимости, this и приведение типов, и умеете ли рассуждать вслух.
Можно ли запустить код во время собеседования?
Обычно нет: смысл задачи в том, чтобы предсказать вывод. Если не уверены, скажите, в чём сомневаетесь, и объясните оба варианта.
Почему в браузере вывод бывает другим?
В обычном скрипте браузера нет строгого режима по умолчанию, а this на верхнем уровне равен window. Поэтому примеры с this и оторванными методами там дают другой результат. Уточните у интервьюера, где выполняется код.
Сколько таких задач нужно решить?
Важнее покрыть все темы: циклы и замыкания, поднятие, this, event loop, приведение типов, ссылки. Когда вы решаете новые задачи на эти темы без ошибок, количество уже не важно.

Билеты с зачётом вместо чтения статей

20 вопросов в билете, зачёт при двух ошибках и меньше, разбор каждой ошибки. Бесплатный тариф без ограничения по времени.

Читайте также