Доброго времени суток, друзья!

Представляю вашему вниманию перевод заметки Nikhil John «console.log isn’t in the JavaScript language».

Пожалуй, console.log является самой используемой командой в JS. Однако она не является частью JS. Не верите? А вы загляните в спецификацию ECMAScript2015.

После того как я вас заинтриговал (потому что вы не обнаружили упоминания о console в ES6), позвольте мне продолжить.

То, что называют JS в браузере/на сервере, это лишь среда выполнения JS. Эта среда представляет собой так называемый «движок» JS (V8 в Chrome и NodeJS, SpiderMonkey в Firefox, JavaScriptCore в Safari).

Среда выполнения JS = движок JS (JS Runtime) + цикл событий (Event Loop) + внешние API + очередь функций обратного вызова (Callback Queue; далее — очередь).

Примечание: цикл событий, в зависимости от реализации, может входить в движок JS.



Объект console — это Web API, предоставляемый в распоряжение движка JS браузером, наряду с такими API, как DOM, Fetch, History, Service Workers и Web Storage. JS работает как в браузере, так и на сервере, и каждый из них имеет собственную реализацию console. Части JS, которые являются стандартными для всех сред выполнения, подробно описываются в спецификациях ECMA (речь идет о таких вещах, как «примитивы», типы данных, грамматика и синтаксис языка, арифметические и логические операции, встроенные объекты и функции и т.д.). Это означает, что одни вещи, такие как, например, Array.isArray(), встроены в JS, другие, такие как setTimeout(), нет.

Давайте посмотрим на реализации console в разных средах выполнения (сперва — Chrome v78, затем — Node v10, оба работают на V8):



Реализация Node по сравнению с реализацией Chrome имеет парочку дополнительных методов (markTimeline, timeline, timelineEnd), хотя обе среды выполнения основаны на спецификации ECMA. Реализация console в Firefox также будет отличаться от представленных.

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

В действительности JS является однопоточным и синхронным языком.

Асинхронные операции (setTimeout, setInterval и др.) предоставляются и реализуются средой выполнения.

В браузере выполнение JS-кода имеет примерно следующий вид (спасибо Philip Roberts за Loop). Обратите внимание, что JS или V8 — прямоугольник под названием «стек вызовов» (Call Stack) слева. Web API и очередь — компоненты среды выполнения браузера.


Это называется параллельной моделью выполнения событий или «Как однопоточный синхронный JS выполняет несколько (якобы) одновременных асинхронных операций». Это также объясняет ответ на популярный вопрос о том, почему код

console.log('Первый')

setTimeout(() => console.log('Второй'), 0)

console.log('Третий')

… выводит

Первый
Третий
Второй

… а не

Первый
Второй
Третий

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

Поскольку setTimeout — это Web API, он запускает внешний таймер в браузере вне среды выполнения JS. Может показаться, что JS выполняет «отложенный» код позже, но вот что происходит на самом деле:

  • ...
  • Цикл событий проверяет очередь (возвращает false; проверка осуществляется постоянно на предмет «непустоты», т.е. наличия чего-либо в очереди).
  • V8 берет setTimeout из стека вызовов и выполняет ее.
  • V8 получает уведомление о необходимости вызова Web API для запуска внешнего таймера (ВТ). ВТ запускается.
  • Цикл событий проверяет очередь (возвращает false).
  • V8 берет из стека вызовов следующую функцию и выполняет ее.
  • ...
  • ВТ заканчивается.
  • Web API помещает функцию обратного вызова (ФОВ) в очередь.
  • Цикл событий проверяет очередь (возвращает ФОВ).
  • Цикл событий помещает ФОВ в стек вызовов.
  • V8 берет ФОВ из стека вызовов и выполняет ее.
  • Цикл событий проверяет очередь (и ничего не возвращает).
  • ...

Вот и все, теперь мы знаем, как однопоточный синхронный JS выполняет асинхронные операции. Вся соль — во внешних API.

Счастливого кодинга!

Также см. серию статей о том, как работает JS, от @ru_vds и заметку «Парочка интересных методов объекта Console».