Представляю вашему вниманию перевод заметки 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».
Vadem
Спасибо кэп!