“Не люблю темные стекла, сквозь них темное небо.
Дайте мне войти, откройте двери.”(Виктор Цой)
Введение
Многим из нас время от времени приходиться менять работу и ходить по собеседованиям. На них соискателям задают каверзные вопросы, ответы на которые не всегда связанны непосредственно с кодированием и требуют более детального и глубокого погружения в предметную область. Сложно сказать насколько это необходимо на предлагаемом месте работы, но после некоторых особо запоминающихся собеседований, комичные ситуации описанные в интернете (ссылка) становятся немного грустными. Посетив разные фирмы в качестве соискателя на позицию Javascript разработчика, я решил поделиться ответами на вопросы, которые мне задавались. Статья состоит из двух частей. В первой части речь пойдет о том, как работает Javascript. Во второй части будет список вопросов с ответом на каждый из них.
Как работает Javascript
Кто-то так сразу и спрашивал: «Как работает Javascript?». Но были и те кто заходил издалека и с помощью наводящих вопросов пытались вытянуть из меня эту информацию. Список вопросов, которые мне задавали:
-
Что такое асинхронность в Javascript?
-
Что такое event loop?
-
Что такое контекст выполнения?
-
Что такое стек вызовов?
-
Что такое JavaScript AST?
Ответы на эти вопросы требуют понимания того, как работает Javascript, что он из себя представляет и как он взаимодействует с окружением. Свою популярность он обрёл благодаря Web браузерам, которые выбрали его в качестве языка сценариев, позволяющих гибко взаимодействовать с пользователем. Javascript – это высокоуровневый язык программирования и для работы с ним у каждого браузера есть специальный JavaScript engine, точнее ECMAScript engine. На данный момент самыми популярными ECMAScript engine являются следующие:
-
Chakra, Microsoft IE/Edge
-
SpiderMonkey, FireFox
-
V8, Chrome
Движки (engine) которые работают с Javascript преобразовывают его в байт код, который потом непосредственно исполняется. Преобразование оригинального кода в byte code “движком” V8 от Chrome выполняется по следующей схеме:

-
Первым шагом, парсер проводит лексический и синтаксический анализ кода. На выходе получается Абстрактное синтаксическое дерево (AST) (ссылка). В AST Explorer можно посмотреть, как выглядит это дерево. Стоит отметить такие узлы дерева как FunctionDeclaration и VariableDeclaration в них хранятся объявления функций и переменных. Парсеры исходного кода в AST могут быть от разных производителей. Ниже список самых популярных среди них:
-
babel-parser
-
espree
-
acorn
-
esprima
-
cherow
-
-
Interpreter на вход получает AST и строит Byte code, при этом вызывая Profiler. Пока работает Profiler, V8 engine исполняет байт код.
-
Profiler в это время проводит оптимизацию кода и передаёт оптимизированный код Compiler’у
-
Compiler создает оптимизированный byte code.
-
Временный byte code заменяется оптимизированным.
Далее написанный код начинает исполняться. Код, написанный на Javascript, выполняется синхронно – одна команда в один момент времени. Код работает в браузере, в котором множество процессов работают параллельно и для взаимодействия с этими процессами придумали следующее:
-
Для каждой html страницы в браузере Javascript выполняется в своем отдельном потоке (Main Thread).
-
Содержимое тега <script>…</script> или содержимое файла переданного в свойстве тега <script src=”file.js”>…</script> начнёт исполняться сразу после загрузки документа в браузер.
-
Для того чтобы взаимодействовать с Web формой в HTML можно назначить функцию обработки на какое-либо событие:
-
В HTML: onclick = ‘myFunc()’
-
В Javascript: element.addEventListener(«click», myFunc(), false);
-
-
Для взаимодействия с другими процессами браузера существует набор интерфейсов Web API (ссылка). При вызове функций этих интерфейсов нужно обязательно в качестве параметра передать функцию обратного вызова (callback function), которой будет передано управление после того, как Web API функция отработает. Эти интерфейсы не ограниченны одним потоком и могут работать параллельно. Кстати, setTimeout() так же является частью Web API.
Далее при наступлении события или по окончанию работы функции Web API, функции обработки события и функции обратного вызова попадают в очередь — Task Queue. Откуда их извлекает и передаёт на исполнение Event Loop. Кроме Task Queue есть ещё:
-
Render Queue, которая отслеживает все изменения DOM модели. На данный момент раз в 16.6 мс. (при 60FPS) происходит перерисовка Web страницы и обновляются все связанные с ней элементы DOM.
-
Microtask Queue, которая выполняет код на Javascript, в функциях Promise.prototype.then() и Promise.prototype.catch(), а так же код который выполнится внутри async функций после выполнения таски с ключевым словом await. Микротаски выполняются сразу после завершения Таски, c которой они связанны. Таким образом это не совсем отдельная очередь. Просто это некий довесок к таске, который должен выполнится сразу после неё. Сюда так же входит код, который выполниться сразу после await, в функции с ключевым словом async.
Event Loop работает с очередями в следующем приоритете:
-
Render Queue
-
Task Queue (или Callback Queue, Macrotask Queue, Event Queue)
-
После каждой выполненной Таски выполняются связанные с ней Микротаски

В различных Web browser’ах могут быть свои особенности реализации Event Loop, но суть и принцип его работы в общем похожи.
В Node.js концепт тот же – внешняя функция выполняется параллельно и возвращает результат своей работы в callback функцию, которая будет вызвана в порядке очереди, но есть отличия. Вместо Web API используется библиотека libuv и Node API. DOM, HTML и Render Queue отсутствуют, а callback функции имеют свой приоритет обработки. Сам Event Loop реализован на функциях библиотеки libuv. Так же она используется для взаимодействия с операционной системой, на которой развернут сервер Node.js.
Согласно документации, Event Loop в Node.js выполняет 6 операций в определенном порядке.
-
timers: в этой фазе выполняются callback функции от функций setTimeout() и setInterval().
-
pending callbacks: выполняются callback функции от системных операций. Как я понял, обработчики ошибок TCP скорее всего будут здесь.
-
idle, prepare: выполняется внутренний код Event Loop
-
poll: на этом шаге выполняется много всего, но для Event Loop важно то, что здесь выполняются callback функции, кроме тех, что в pending callbacks и close callbacks.
-
check: выполняются callback функции от setImmediate()
-
close callbacks: callback функции закрывающие различные соединения (socket.on(‘close’, …) и т.п.).
Помимо операций выше, существуют callback функции от process.nextTick(), которые выполняются сразу после завершения текущей таски. Так же код, который содержится в Promise.prototype.then() и Promise.prototype.catch() и в функции async после await, будет выполнятся сразу после таски которая связанна с этим кодом (Microtasks).
Все эти 6 шагов можно найти в коде libuv. Задача libuv поддерживать асинхронный ввод/вывод, основанный на цикле событий. Причем сделать так, чтобы это все могло работать под различными операционными системами. Отсюда появляется шаг “pending callbacks” и “idle, prepare”, которые появились благодаря нюансам работы операционных систем. Эти 2 шага находятся между Timers и Poll. Получается, что прежде, чем вызвать обычные callback функции сначала вызываются системные, потом небольшая задержка и только потом остальные. Для упрощения схемы можно объединить «pending callbacks», «idle, prepare» и «poll» в один большой шаг, в котором вызываются callback функции. В итоге получается следующая схема:

Заключение:
Надеюсь, что после описания того, как это работает ответить на вопросы будет не так сложно. Итак, краткие ответы на вопросы, которые были озвучены вначале:
-
Что такое асинхронность в Javascript?
Callback функции и механизм работы с ними в Javascript. (ссылка)
-
Что такое event loop?
Event loop — это бесконечный цикл в котором движок JavaScript ожидает задачи и исполняет их. (ссылка)
-
Что такое контекст выполнения?
Контекст выполнения – специальная внутренняя структура данных, которая содержит информацию о вызове функции и включает в себя:
-
конкретное место в коде, на котором находится интерпретатор;
-
локальные переменные функции;
-
значение this;
-
прочую служебную информацию. (ссылка)
-
-
Что такое стек вызовов?
стек вызовов — это структура данных, которая используется для хранения контекстов выполнения, создаваемых в ходе работы кода. Стек выполнения действует по принципу “первым вошел последним вышел”. (ссылка)
-
Что такое JavaScript AST?
AST (Абстрактное синтаксическое дерево) — древовидное представление исходного кода. (ссылка)
Краткие ответы скорее всего не удовлетворят интервьюера и вам придётся углубляться в детали. В этой статье я попытался дать необходимое и, на мой взгляд, достаточное описание того, как это работает. Для более детального погружения во все тонкости и нюансы работы Javascript предлагаю изучить материалы по ссылкам ниже. При написании статьи я так же опирался на эти источники.
P.S.
Не стоит забывать и про «глупые вопросы», которые вам могут задать на собеседовании (ссылка). Порой они не такие уж и глупые (ссылка). В этом плане мне очень понравился анекдот:
После лекции для HR-специалистов одна из слушательниц спрашивает у докладчика:
— Собеседования отнимают очень много времени. Скажите, как можно максимально быстро определить, что за человек перед тобой — идиот или нормальный?
— Конечно. Задайте ему какой-нибудь простейший вопрос. Например: «Известно, что Кук совершил три путешествия, во время одного их них он погиб. Во время какого именно?»
— А можно какой-нибудь другой пример? А то у меня в школе было плохо с географией. (ссылка)
Ссылки:
https://habr.com/ru/post/439564/
https://habr.com/ru/post/439564/
https://blog.bitsrc.io/javascript-under-the-hood-632ccae06b27
https://nuancesprog.ru/p/4553/
https://blog.bitsrc.io/javascript-under-the-hood-632ccae06b27
https://dev-gang.ru/article/kak-rabotaet-javascript-pod-kapotom-dvizhka-v-5ew7muxdnq/
https://medium.com/@deedee8/event-loop-cycle-in-node-js-bc9dd0f2834f
https://aldizhupani.medium.com/javascript-async-await-microtask-queue-explained-9844f010bb0f
http://imnotgenius.com/21-sobytijnyj-tsikl-biblioteka-libuv/
http://docs.libuv.org/en/v1.x/design.html
https://russianblogs.com/article/51951362749/
https://habr.com/ru/post/336498/
https://snyk.io/blog/nodejs-how-even-quick-async-functions-can-block-the-event-loop-starve-io/
https://habr.com/ru/post/479062/
https://nexocode.com/blog/posts/behind-nodejs-event-loop/
https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/
https://developer.mozilla.org/ru/docs/Web/API
https://javascript.info/event-loop
https://habr.com/ru/company/hh/blog/517594/
https://hackernoon.com/is-javascript-a-single-threaded-language-w6v3ujb
https://bool.dev/blog/detail/obyasnenie-event-loop-v-javascript-s-pomoshchyu-vizualizatsii
https://developer.mozilla.org/ru/docs/Web/API/window/requestAnimationFrame
https://frarizzi.science/journal/web-engineering/browser-rendering-queue-in-depth
ссылка на оригинал статьи https://habr.com/ru/post/646847/
Добавить комментарий