Вам действительно не нужно много знать, чтобы начать работу с Javascript. Фактически, полноценное готовое к работе приложение может быть создано без понимания внутренней работы языка. Это одновременно и благословение, и проклятие; У языка много последователей, но очень мало мастеров.

Это осознание привело меня к поискам попыток понять, как на самом деле работает Javascript. Путешествие еще далеко, но туман начал рассеиваться. И все началось с вопроса: «Что такое Javascript?».

Так что же такое Javascript? Формально Javascript - это однопоточный неблокирующий асинхронный язык программирования.

Чего ждать?

На первый взгляд, это определение (по крайней мере, для меня) было противоречивым, запутанным и просто приводило к большему количеству вопросов. Как может однопоточная программа быть неблокирующей? Как это могло быть еще и асинхронным?

Все это очень важные вопросы; Но потерпите меня, у них тоже есть ответы. Ответы, которые лежат в основе Javascript, его среды выполнения.

Среда выполнения Javascript

Среда выполнения Javascript - это то, что заставляет ваш код работать, это то, что позволяет Javascript иметь всю сложную терминологию в своих формальных определениях, и в контексте браузера это в основном комбинация 4 вещей:

  1. Движок Javascript
  2. Веб-API
  3. Очередь обратного вызова
  4. Цикл событий

Разберем каждую из этих частей по отдельности.

Примечание

Среда выполнения Javascript может принимать разные формы и формы в зависимости от контекста. Например, среда выполнения браузера (которую мы рассмотрим подробно) отличается от среды выполнения node.js. Однако эти различия в основном связаны с реализацией; Обсуждаемые нами концепции применимы к большинству сред выполнения Javascript независимо от контекста.

Механизм Javascript

Механизм Javascript - это мозг среды выполнения, и его основная цель - преобразовать ваши понятные для человека сценарии в машиночитаемый код.

Он также управляет стеком вызовов (представьте себе структуру данных стека LIFO), чтобы отслеживать выполнение вашего кода, а само выполнение всегда выполняется в одном потоке, синхронно. Вдобавок движок выполняет сборку мусора, оптимизацию и множество других вещей, которые заслуживают отдельной статьи.

Любой движок, достойный вашего времени, также будет содержать реализацию ECMAScript, спецификации языка сценариев, созданной для стандартизации Javascript. Это то, что позволяет движку «знать», что такое цикл while и что Math.round(x) должен делать. Если все, что вам нужно, это набор циклов while, несколько определений функций и некоторые переменные (другими словами, если то, что вам нужно, определено в спецификации ECMAScript), механизм Javascript вполне способен обрабатывать ваш код самостоятельно.

Но Javascript - это гораздо больше, чем просто несколько определений функций. Это мощный язык, способный делать множество вещей, что подводит нас к следующему фрагменту головоломки - веб-API.

Веб-API

Если вы просмотрите исходный код движка Javascript, такого как V8, вы поймете, что многих функций, которые, как вы считали, были частью стандартной спецификации Javascript, просто нет. Речь идет о функции setTimeout. Не знаю, как вы, но для меня setTimeout - это стандартный Javascript. Но если его нет в двигателе, где это?

Введите веб-API. Веб-API - это ключевые реализации для конкретных браузеров, расширяющие язык Javascript. Например, если вы откроете консоль Google Chrome и наберете что-то вроде:

function main(){ 
  setTimeout(()=>console.log('Hello World!'), 5000); 
}; 
main();

«Hello World» будет напечатано на консоли через 5 секунд, как и ожидалось. Этот код работает, потому что веб-API Google Chrome имеют собственную реализацию setTimeout, встроенную в его механизм Javascript V8. Точно так же модель DOM не является частью механизма Javascript. Все, что связано с DOM: будь то прослушиватели событий, средства доступа или манипуляторы, все определяется в ... как вы уже догадались, веб-API!

Здесь возникает очень логичный вопрос: если движок Javascript не знает об этих функциях, то как он будет обрабатывать вызов функции «веб-API» в моем скрипте?

Ответ заключается в том, как браузер встраивает веб-API в свой механизм Javascript.

Когда механизм Javascript пытается разрешить символ, он запускается в локальной области и поднимается по цепочке областей. В самом конце цепочки находится область global. В рамках инициализации механизма Javascript любая среда хоста может добавлять свои собственные API-интерфейсы в эту global область видимости, тем самым открывая функцию и соответствующий ей обработчик механизму Javascript.

Чтобы лучше это понять, вернемся к нашему примеру:

function main(){ 
  setTimeout(()=>console.log('Hello World!'), 5000); 
}; 
main();

Что на самом деле происходит, когда этот код копируется в браузер Google Chrome?

  • Двигатель V8 запустится с добавления main() в стек вызовов.
  • Затем движок будет искать определение функции setTimeout в цепочке областей видимости. (Это будет доступно в области global, поскольку setTimeout не встроено в движок).
  • Механизм V8 отправит setTimeout() в стек вызовов, «вызовет» определение setTimeout веб-API, которое, в свою очередь, инициирует любую встроенную реализацию браузера, имеющуюся для того же, вне основного потока Javascript. .
  • На данный момент, что касается двигателя, его работа сделана. Следовательно, setTimeout() будет извлечен из стека вызовов, за которым сразу следует main().

Хорошо. Но этого не может быть, правда? После 5-секундной задержки по-прежнему должен быть выполнен обратный вызов. Что касается обратного вызова, что с ним произошло на самом деле?

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

Очередь обратного вызова

Так куда делся этот проклятый обратный звонок?

Прежде чем мы перейдем к этому, нам нужно сначала понять роль функции обратного вызова.

Функция обратного вызова, по сути, является связью между механизмом Javascript и асинхронной задачей веб-API, обычно с инструкциями о том, что делать после того, как асинхронная задача выполнила свои обязанности. Любой фрагмент кода, который выполняется вне основного потока движка, всегда будет требовать связанный обратный вызов для обратной связи с движком.

Если вы еще этого не осознали, это фундаментальная концепция асинхронного Javascript.

Совет

Все, что выполняется вне основного потока механизма Javascript, является асинхронной операцией.

Хорошо, это все хорошо и все такое, но серьезно, где наш обратный звонок?

Давайте немного перемотаем назад.

После того, как механизм V8 «вызывает» setTimeout привязку веб-API, некоторому собственному коду браузера будет поручено вычислить 5-секундную задержку. Это асинхронная операция, которая происходит вне основного потока движка, и детали ее реализации не важны. Этот же собственный код также будет отвечать за то, чтобы наша «потерянная» функция обратного вызова вернулась домой.

Так как же это сделать?

Что ж, в Javascriptland есть специальное место для обратных вызовов, ожидающих выполнения. Это очередь обратного вызова, и это одна из двух важных частей среды выполнения Javascript, которая делает возможным асинхронный Javascript.

Собственный код знает о его существовании, и как только асинхронная задача будет завершена, он поместит нашу функцию обратного вызова в очередь обратного вызова.

Наша функция обратного вызова почти готова, но мы еще не дошли до нее. В головоломке времени выполнения Javascript остался еще один последний кусочек.

Цикл событий

Что, черт возьми, за цикл обработки событий?

Цикл событий - это гель между механизмом Javascript и очередью обратного вызова, и он выполняет две функции.

Во-первых, он будет периодически проверять, пуст ли стек вызовов движка Javascript. Если это так, он затем получит обратный вызов из очереди обратного вызова и поместит его в стек вызовов, эффективно планируя его выполнение.

Это действительно так просто.

Вместе очередь обратного вызова и цикл событий - это то, что заставляет ваш асинхронный код Javascript работать. Первый будет ставить в очередь функции обратного вызова для завершенных асинхронных операций, а второй будет планировать их выполнение в основном потоке Javascript.

Полный круг

«… однопоточный неблокирующий асинхронный язык программирования».

Я надеюсь, что это было довольно информативное чтение о том, что на самом деле происходит под капотом Javascript, и приведенное выше определение имеет немного больше смысла, чем то, что было сделано несколько минут назад.

Я старался делать вещи очень обобщенными; Просто чтобы убедиться, что вы понимаете, что эти концепции применимы не только к среде выполнения вашего браузера, но и к любой среде выполнения Javascript, с которой вы можете столкнуться.

Я также почти уверен, что это откроет много новых вопросов, которых у вас, возможно, не было раньше. Но в этом все дело. Вопросы должны привести к исследованию, которое может привести к еще большей путанице, но если вы будете упорствовать, в конечном итоге вы найдете ответы. Вот как вы учитесь.

Итак, чтобы резюмировать весь текст в несколько пунктов:

  • Среда выполнения Javascript - это то, что заставляет ваш код Javascript работать. Он может принимать разные формы и формы (браузеры, node.js); Но фундаментальные концепции среды выполнения останутся неизменными во всех средах. Среда выполнения браузера состоит из механизма Javascript, набора веб-API, очереди обратного вызова и цикла событий.
  • Механизм Javascript преобразует ваш читаемый человеком код Javascript в машиночитаемый байт-код и всегда выполняет их в одном потоке. Он также управляет стеком вызовов и делает множество других вещей.
  • Веб-API расширяют язык Javascript за счет собственных функций, которые добавляются к объекту global механизма Javascript. Некоторые из этих функций синхронны, некоторые могут быть асинхронными.
  • Очередь обратных вызовов помещает в очередь обратные вызовы, ожидающие выполнения механизмом Javascript. Обратный вызов обычно всегда связан с некоторой асинхронной операцией.
  • Цикл событий - это гель между механизмом Javascript и очередью обратного вызова. Его задача - переместить обратные вызовы из очереди обратных вызовов в стек вызовов движка для выполнения.
  • Очередь обратного вызова и цикл событий лежат в основе асинхронногонеблокирующего!) Характера Javascript.

Ресурсы

  1. Если вам нравится смотреть, а не читать, я настоятельно рекомендую проверить что, черт возьми, за цикл событий? Филипа Робертса. Серьезно, проверьте это.
  2. Этот пост о переполнении стека обсуждает связь между движком V8 и веб-API, а этот - о очереди обратного вызова.

Первоначально опубликовано на https://www.blog4js.com