Как работает стек
Что проверяют
Для фронтенда вопрос почти всегда про стек вызовов JavaScript и его место в event loop, а не про учебную структуру данных. Следом обычно дают код с setTimeout, Promise.then и console.log и просят назвать порядок вывода.
Как отвечать
Коротко про структуру: стек работает по принципу LIFO. В JavaScript его роль играет массив: push и pop работают за O(1), а shift и unshift сдвигают все индексы и стоят O(n). Классические задачи: проверка скобок, отмена действий в редакторе, обход DOM в глубину без рекурсии.
Дальше главное, стек вызовов движка:
- кадр на вызов: вызов функции кладёт кадр с аргументами, локальными переменными и точкой возврата,
returnего снимает. Основной поток один, и пока стек не пуст, ничего другого не выполняется, включая отрисовку страницы; - переполнение: бесконечная рекурсия даёт
RangeError: Maximum call stack size exceeded. Предельная глубина зависит от движка и размера кадров, обычно это тысячи или десятки тысяч вызовов; - event loop: колбэки таймеров, событий и сетевых ответов ждут в очереди задач и попадают в стек, только когда он опустел. Перед следующей задачей выполняются все микрозадачи:
Promise.then,queueMicrotask, продолжения послеawait; - асинхронность: после
awaitфункция продолжается уже в новом стеке, поэтомуtry/catchвокруг вызова безawaitне ловит ошибку внутри промиса.
Порядок в классической задаче: сначала синхронный код, потом все микрозадачи, потом одна задача из очереди, например колбэк setTimeout(fn, 0).
Где спотыкаются
Путают стек вызовов с кучей и с очередью задач. Объекты лежат в куче, в кадре только ссылки на них, а очередь хранит то, что ещё не начало выполняться.
Надеются на хвостовую рекурсию. Оптимизацию хвостовых вызовов из ES2015 реализует только Safari, в Chrome и Node её нет, поэтому глубокую рекурсию переписывают на цикл с явным стеком.
Думают, что setTimeout(fn, 0) выполнится сразу. Он встанет в очередь и отработает после синхронного кода и всех микрозадач, а при глубоко вложенных таймерах браузер поднимает задержку до 4 мс.