Как стать автором
Обновить

Собеседование по Javascript, мой опыт. Часть первая

Время на прочтение7 мин
Количество просмотров28K

“Не люблю темные стекла, сквозь них темное небо.
Дайте мне войти, откройте двери.”

(Виктор Цой)

Введение

Многим из нас время от времени приходиться менять работу и ходить по собеседованиям. На них соискателям задают каверзные вопросы, ответы на которые не всегда связанны непосредственно с кодированием и требуют более детального и глубокого погружения в предметную область. Сложно сказать насколько это необходимо на предлагаемом месте работы, но после некоторых особо запоминающихся собеседований, комичные ситуации описанные в интернете (ссылка) становятся немного грустными. Посетив разные фирмы в качестве соискателя на позицию 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 выполняется по следующей схеме:

  1. Первым шагом, парсер проводит лексический и синтаксический анализ кода. На выходе получается Абстрактное синтаксическое дерево (AST) (ссылка). В AST Explorer можно посмотреть, как выглядит это дерево. Стоит отметить такие узлы дерева как FunctionDeclaration и VariableDeclaration в них хранятся объявления функций и переменных. Парсеры исходного кода в AST могут быть от разных производителей. Ниже список самых популярных среди них:

    1. babel-parser

    2. espree

    3. acorn

    4. esprima

    5. cherow

  2. Interpreter на вход получает AST и строит Byte code, при этом вызывая Profiler. Пока работает Profiler, V8 engine исполняет байт код.

  3. Profiler в это время проводит оптимизацию кода и передаёт оптимизированный код Compiler’у

  4. Compiler создает оптимизированный byte code.

  5. Временный byte code заменяется оптимизированным.

Далее написанный код начинает исполняться. Код, написанный на Javascript, выполняется синхронно – одна команда в один момент времени. Код работает в браузере, в котором множество процессов работают параллельно и для взаимодействия с этими процессами придумали следующее:

  1. Для каждой html страницы в браузере Javascript выполняется в своем отдельном потоке (Main Thread).

  2. Содержимое тега <script>…</script> или содержимое файла переданного в свойстве тега <script src=”file.js”>…</script> начнёт исполняться в процессе загрузки документа в браузер. Причём подгрузиться он может раньше, чем HTML-элементы на этой странице (ссылка).

  3. Для того чтобы взаимодействовать с Web формой в HTML можно назначить функцию обработки на какое-либо событие:

    1. В HTML: onclick = ‘myFunc()’

    2. В Javascript: element.addEventListener("click", myFunc(), false);

  4. Для взаимодействия с другими процессами браузера существует набор интерфейсов 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 работает с очередями в следующем приоритете:

  1. Render Queue

  2. Task Queue (или Callback Queue, Macrotask Queue, Event Queue)

  3. После каждой выполненной Таски выполняются связанные с ней Микротаски

В различных 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 операций в определенном порядке.

  1. timers: в этой фазе выполняются callback функции от функций setTimeout() и setInterval().

  2. pending callbacks: выполняются callback функции от системных операций. Как я понял, обработчики ошибок TCP скорее всего будут здесь.

  3. idle, prepare: выполняется внутренний код Event Loop

  4. poll: на этом шаге выполняется много всего, но для Event Loop важно то, что здесь выполняются callback функции, кроме тех, что в pending callbacks и close callbacks.

  5. check: выполняются callback функции от setImmediate()

  6. 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://astexplorer.net/

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/nuances-of-programming/%D0%BA%D0%B0%D0%BA-%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D0%B0%D0%B5%D1%82-javascript-cdbef3f20a66

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/461401/

Теги:
Хабы:
Всего голосов 3: ↑2 и ↓1+1
Комментарии21

Публикации

Истории

Работа

Ближайшие события