Задачи с кодом
Что выведет код: 10 задач JavaScript с собеседования и разбор
Содержание
- Коротко
- Как решать такие задачи вслух
- 1. var и let в цикле с setTimeout
- 2. Поднятие и временная мёртвая зона
- 3. this в методе, стрелке и оторванной функции
- 4. Микрозадачи и таймер
- 5. async/await и executor промиса
- 6. Микрозадача внутри таймера
- 7. Приведение типов
- 8. Замыкание счётчика
- 9. typeof
- 10. Объекты и массивы передаются по ссылке
- Как готовиться к вопросам «что выведет»
- Частые ошибки
- Чек-лист
- Вопросы из базы
- Как тренировать
- Частые вопросы
Вопрос «что выведет код» звучит почти на каждом frontend-собеседовании: интервьюер показывает 5-15 строк и просит назвать вывод консоли по порядку. Так за пару минут проверяют, понимаете ли вы области видимости, this, event loop и приведение типов. Ниже 10 типичных задач, у каждой настоящий вывод Node.js и разбор, почему он такой.
Как решать такие задачи вслух
Главное правило: не угадывайте, а исполняйте код в голове построчно. Проговаривайте, что происходит на каждой строке. Если ошиблись в одном месте, интервьюер увидит, что ход мысли верный, и часто подскажет.
- Сначала найдите всё синхронное: обычные вызовы console.log идут первыми.
- Потом микрозадачи: then, catch, finally, queueMicrotask и продолжение после await.
- Потом таймеры: setTimeout и setInterval, в порядке постановки.
- Отдельно отметьте, где объявлена переменная и как вызвана функция: от этого зависят значение и this.
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.
Как готовиться к вопросам «что выведет»
- Сначала пишите ответ сами, на бумаге или в заметке. Только потом запускайте код.
- Если ошиблись, найдите правило, которое нарушили, и запишите его одной фразой.
- Через несколько дней решите ту же задачу снова. Правило должно вспоминаться само.
- Решайте вслух. На собеседовании ценят объяснение «почему», а не только верные цифры.
Такие задачи часто идут блоком на техническом скрининге. Остальные темы junior собраны в разделе что должен знать junior frontend.
Частые ошибки
- Угадывать вывод целиком, а не исполнять код построчно
- Считать, что setTimeout с нулём выполнится раньше промиса
- Забывать, что тело async-функции до первого await синхронное
- Путать this стрелки и обычной функции
- Не уточнить, где выполняется код: модуль, скрипт браузера или Node
Чек-лист
- Объясняете разницу var, let и const с примером цикла
- Знаете порядок: синхронный код, микрозадачи, таймеры
- Можете сказать, чему равен this в четырёх случаях вызова
- Помните основные правила приведения при плюсе, минусе и нестрогом сравнении
- Понимаете, почему объекты сравниваются по ссылке
Вопросы из базы Техпрогона
Частые вопросы по теме с ответом и разбором. Откройте любой и попробуйте ответить сами.
- Чем отличаются var, let и constJavaScript
- Неявное приведение типов в JavaScriptJavaScript
- Что такое замыканиеJavaScript
- Всплытие и TDZJavaScript
- Объясните замыкание на примереJavaScript
- Объясните event loopJavaScript
- Event loopJavaScript
- Разница между == и ===JavaScript
- Что возвращает typeof nullJavaScript
- Можно ли менять объект в constJavaScript
Как тренировать в Техпрогоне
- В каталоге JavaScript больше сотни вопросов типа «что выведет код» с разбором каждой строки.
- Билеты с зачётом по JavaScript: 20 вопросов, зачёт при двух ошибках и меньше, ошибки показываются с разбором сразу после ответа.
- Ошибки попадают в повторения: вопрос вернётся через несколько дней, пока правило не закрепится. Раздел «Мои ошибки» и марафон по треку доступны в тарифе «Рывок».
- Перед каждой темой можно прочитать короткий урок: все уроки бесплатны.
Частые вопросы
Зачем на собеседовании спрашивают, что выведет код?
Можно ли запустить код во время собеседования?
Почему в браузере вывод бывает другим?
Сколько таких задач нужно решить?
Билеты с зачётом вместо чтения статей
20 вопросов в билете, зачёт при двух ошибках и меньше, разбор каждой ошибки. Бесплатный тариф без ограничения по времени.
Читайте также
Задачи на frontend-собеседовании
debounce, throttle, Promise.all, deepEqual, EventEmitter, flatten и useDebounce: условие, решение на JavaScript, что проверяют и какие будут уточнения.
Код-ревью на собеседовании
Как проходит код-ревью на собеседовании frontend: компонент с восемью проблемами, порядок ответа, вежливые формулировки замечаний и исправленная версия.
Как запомнить ответы
Почему прочитанное забывается к собесу и что работает лучше: проверка себя, интервальное повторение, ответ вслух своими словами и билеты с зачётом.
Технический скрининг
Формат, типичные вопросы и план подготовки
Урок: event loop
Микрозадачи, таймеры и порядок выполнения
Все статьи
Первая работа, подготовка к собесу и задачи с кодом