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

(Виктор Цой)

Введение

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

  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/

Комментарии (15)


  1. Metotron0
    31.01.2022 17:14

    Такие знания кишков JS требуют даже если пришёл всякие CRM на фреймворке писать? Не пугайте меня, у меня впереди маячат собеседования, а я там глубоко не погружался.


    1. alex_29 Автор
      31.01.2022 17:44

      Я, когда собеседовался, сам этому удивился. Причём такой глубины не требовали ни в React, ни в Angular. У меня даже была мысль написать об React, Angular и Vue.js в рамках статьи о собеседованиях. Но смутило то, что о них особо не спрашивали. Пару простых вопросов и всё. Но тут все индивидуально.


      1. Xambey97
        31.01.2022 18:37
        +1

        Ну смотря где, в топовые фирмы, куда я собеседовался, меня очень подробно распрашивали как минимум о том, как работает event loop (все что вы описал тс, и еще большие тонкости), не считая других особенностей работы JS. В одной из них я сейчас работаю и очень доволен тем, что уровень всех моих коллег на моем и выше уровнем, что довольно приятно, будучи старшим разрабом. Собеседовался на angular разработчика. Уровень фильтров большинства компаний, куда я пробовался прошлым летом оставляет желать лучшего… А потом удивляемся, как это народ умудряется сделать на столько тормозные сайты, используя готовые фреймворки…


    1. JustDont
      31.01.2022 17:53
      +6

      Любители писать на фреймворке обычно так же любят в какой-то момент сказать "это не я, это фреймворк тормозит" и гордо свалить в закат. А тебе потом разбираться, в что же они на этот раз не смогли и делать так, чтоб фреймворк не тормозил.


      Поэтому да, некоторые знания "кишков JS" обычно говорят много хорошего о кандидате.


    1. Zibx
      31.01.2022 22:26

      У JS не так много кишков, понимание деревьев в разы сложнее знания замыканий и эвентлупа. Вот про AST сомневаюсь что кто-то спросит. Хотя я спрашивал, но у нас проект был про разработку IDE понимающей js.


      1. Metotron0
        01.02.2022 11:59

        Ну, раз так, то где мне почитать, в каком порядке выполняются setTimeout и промисы?


    1. QeqReh
      01.02.2022 10:30
      +2

      Из-за незнания таких основ, мы как пользователи получаем тормозные сайты и утечку памяти во фронтах.

      Не достаточно пользоваться фреймворком, надо понимать что ты делаешь.


      1. Metotron0
        01.02.2022 12:06
        -1

        Я объявляю computed-свойство в vue, для этого мне не принципиально знать, реализовано оно черкз геттеры с сеттерами или через Proxy, потому что я всё равно на это не влияю и уж точно не буду переписывать часть vue, чтобы стало быстрее. Это другой уровень задач.

        Думаю, стоит исходить ещё и из того, какого уровня задачи будут ставиться на работе, где задают эти вопросы. Если это магазинчик, который пишется-верстается за месяц, то есть ли там что-то, что может тормозить и для чего нужно погружаться во все эти тонкости? О вакансиях какого диапазона мы говорим? Кажется, о CRM на фреймворке у разных людей разное представление. Я, например, нацелен на зарплату 100-150 тысяч, чтобы надо мной был опытный коллега, который в случае тех же тормозов что-то посоветует.


        1. QeqReh
          01.02.2022 12:20

          нацелен на зарплату 100-150 тысяч

          Для такого уровня зп знать основы js - обязательны.

          Знание особенностей работы js, это именно основы.

          Я объявляю computed-свойство в vue, для этого мне не принципиально
          знать, реализовано оно черкз геттеры с сеттерами или через Proxy, потому
          что я всё равно на это не влияю и уж точно не буду переписывать часть
          vue, чтобы стало быстрее. Это другой уровень задач.

          Вам так же ни кто не запрещает использовать замыкания, а большая часть утечек именно из-за их использования и не знания как работает сборщик мусора в js.


          1. Metotron0
            01.02.2022 14:12

            Основы основам рознь. Не для всех основы — это понимание работы движка на уровне регистров процессора. Может быть, я допускаю какие-то ошибки из-за своего незнания чего-то, а может быть, я благодаря опыту избегаю их, даже не зная, что я их избежал.


          1. interhin
            02.02.2022 07:45

            Знать основы JS != знать все тонкости движка и сборщика мусора. Да, если ты это уже знаешь, это большой плюс для каких-то компаний, но упарываться на этом точно не будут.

            У меня помню спрашивали об утечках памяти на собесе на 150-200к. О том как они выявляются, как их избежать. Я просто сказал честно что опыта в этом не было, но знаю что это можно сделать с помощью вкладки Perfomance, что обработчики событий нужно удалять, если удаляется компонент. Мне сказали что многие кандидаты даже не слышали о вкладке Perfomance и то что я знаю даже такое это уже плюс. Ну и спрашивали о том как примерно работает сборщик мусора, как он определяет какие объекты очищать, на что тоже не так сложно ответить, об этом поверхностно пишется на том же learn.javascipt.ru. (P.S. по итогам собеса оффер мне дали).

            Основы JS для меня это Event loop, замыкания, контекст и т.д. Этого вполне достаточно чтобы получать 100-150к, имхо. Хотя с нулевым опытом тебе даже с этими знаниями не будут скорее всего платить столько. (я по регионам сужу, в МСК может и готовы)


            1. alex_29 Автор
              02.02.2022 07:49

              Спасибо за ваш опыт собеседований. Я просто описал то, о чем спрашивали меня.


            1. Metotron0
              02.02.2022 15:00

              У меня опыт не нулевой, я для себя программирую уже 15 лет (не считая школьных экспериментов), а за деньги — примерно 8 лет, из них с осознанным применением JS — плюс-минус 6 лет, потому что я постепенно вкатывался, переходя из бэкенда всё больше во фронтенд. Например, в 2007 я использовал ajax копипастой из учебника, году в 2015-2016 наконец дошёл до учебника по jQuery, а в 2016-2017 узнал про fetch. Но всё это изучение было не очень систематичным, потому что из полноценных учебников по JS я прочитал лишь "ES6 и не только", остальное — MDN отдельными частями.

              Хотелось бы книжку, где объяснят, как работает сборщик мусора и прочие внутренности, но не в терминах функций на C, потому что C у меня в очень зачаточном состоянии, я им баловался месяц году в 2001 и всё.

              Сейчас читаю "Программируй & типизируй", там упомянули про event loop, что это как массив, содержащий функции, даже пример такого массива привели. Но на таком уровне я и без этого понимал, это не добавляет знаний того, в каком порядке выполняются промисы и setTimeout.


        1. JustDont
          01.02.2022 12:59
          +2

          Я объявляю computed-свойство в vue, для этого мне не принципиально знать, реализовано оно черкз геттеры с сеттерами или через Proxy, потому что я всё равно на это не влияю

          Ну вот соседние пацаны с редаксом берут и пишут что-то, скажем, положение скролла в редакс-стор. И ходят потом довольные, им ведь не обязательно знать, что теперь при каждом изменении скролла будут безусловно отрендерены все компоненты, у которых это свойство стора поставлено в зависимости.
          А когда потом простенький проект ставит на колени процессоры офисных компьютеров (потому что реактовский рендер — это потенциально очень большой объем вычислительной работы, даже когда внешне ничего и не меняется) — делают вид, что они тут не при чем. В самом деле, не будут же они редакс переписывать. Что? Оптимизировать вызовы рендера? Воткнуть дебаунс на худой конец (сразу, а не когда всё начало тормозить)? Не, это же другой уровень задач.


          Если это магазинчик, который пишется-верстается за месяц, то есть ли там что-то, что может тормозить и для чего нужно погружаться во все эти тонкости?

          Вы думаете, что нельзя легким движением руки сделать тормозным то, что в общем-то ни при каких обстоятельствах не должно тормозить? Можно.


          1. Metotron0
            01.02.2022 14:10

            Наверное можно, если поставить перед собой такую цель. Но не понимаю, зачем её ставить.