Веб-разработчики или Front-end инженеры, как мы любим называть себя, в настоящее время делают все правильно: от использования в качестве источника интерактивности внутри браузера до создания компьютерных игр, виджетов рабочего стола, кроссплатформенных мобильных приложений или написания их на стороне сервера ( наиболее популярно с node.js), чтобы связать его с любой базой данных, что почти повсеместно используется в качестве языка сценариев. Поэтому важно знать внутреннее устройство Javascript, чтобы использовать его лучше и эффективнее, и именно об этом статья.

Экосистема Javascript стала более сложной, чем когда-либо, и будет продолжать расширяться. Инструменты, необходимые для создания современного веб-приложения, включают Webpack, Babel, ESLint, Mocha, Karma, Grunt и т. Д. И т. Д. И т. Д. Я нашел этот веб-комикс, который прекрасно иллюстрирует борьбу современных веб-разработчиков.

Помимо всего этого, что каждый разработчик Javascript в первую очередь должен сделать, прежде чем углубиться в использование какой-либо инфраструктуры или библиотеки на рыбном рынке, - это знать базовую основу того, как все это делается внутри на корневом уровне. В основном все разработчики JS могли слышать термин V8, среда выполнения chrome, но некоторые могли даже не понимать, что это значит и что это значит. Изначально в первый год моей профессиональной карьеры в качестве разработчика я тоже мало знал обо всех этих причудливых терминах, поскольку в первую очередь речь шла о завершении работы. Это не удовлетворило мое любопытство по поводу того, как, черт возьми, Javascript может делать все это. Я решил покопаться в гугле и наткнулся на несколько хороших сообщений в блоге, в том числе Филип Робертс, отличный доклад на JSConf о цикле событий, и поэтому я решил обобщить свои знания и поделиться им. Так как есть много чего знать, я разделил статью на 2 части. В этой части будут представлены обычно используемые термины, а во второй части - связь между ними.

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

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

Когда мы запускаем указанный выше файл, мы сначала ищем основную функцию, с которой начнется все выполнение. В приведенном выше примере он начинается с console.log (bar (6)), который помещается в стек, следующий верхний кадр - это функция bar с это аргументы, которые, в свою очередь, вызывают функцию foo, которая снова помещается в верхнюю часть стека, и она немедленно возвращается, и поэтому выскакивает из стека, аналогично затем выскакивает панель, и, наконец, выскакивает консольный оператор печать вывода. Все это происходит в мгновение ока (в миллисекундах) по одному.

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

Иногда мы попадаем в бесконечный цикл, поскольку мы вызываем функцию несколько раз рекурсивно, а что касается браузера Chrome, существует ограничение на размер стека, которое составляет 16000 кадров, более того, он просто убьет вас и выбрасывает Достигнута максимальная ошибка стека (изображение ниже).

2.Heap: объекты размещаются в куче, то есть в основном неструктурированной области памяти. Здесь происходит все выделение памяти для переменных и объектов.

3.Queue: среда выполнения JavaScript содержит очередь сообщений, которая представляет собой список сообщений, которые должны быть обработаны, и связанные с ними функции обратного вызова, которые необходимо выполнить. Когда стек имеет достаточную емкость, сообщение извлекается из очереди и обрабатывается, что состоит из вызова связанной функции (и, таким образом, создания начального кадра стека). Обработка сообщения заканчивается, когда стек снова становится пустым. Проще говоря, эти сообщения ставятся в очередь в ответ на внешние асинхронные события (например, щелчок мышью или получение ответа на HTTP-запрос), если предусмотрена функция обратного вызова. Если бы, например, пользователь нажал кнопку, а функция обратного вызова не была предоставлена, сообщение не было бы поставлено в очередь.

Цикл событий

По сути, когда мы оцениваем производительность нашего JS-кода, так что функция в стеке делает его медленным или быстрым, console.log() будет быстро, но выполнение итерации с for или while над тысячами или миллионами строк будет медленнее, и стек будет занят или заблокирован. Это называется сценарием блокировки, о котором вы читали или слышали в Webpage Speed ​​Insights.

Сетевые запросы могут быть медленными, запросы изображений могут быть медленными, но, к счастью, запросы к серверу могут выполняться через AJAX, асинхронную функцию. Если предположить, что эти сетевые запросы выполняются через синхронные функции, что тогда произойдет? Сетевые запросы отправляются на какой-то сервер, который, по сути, является где-то другим компьютером / машиной. Теперь компьютеры могут медленно отправлять ответ. Между тем, если я нажму какую-нибудь кнопку с призывом к действию или потребуется выполнить другой рендеринг, ничего не произойдет, поскольку стек заблокирован. В многопоточном языке, таком как Ruby, с этим можно справиться, но в однопоточном языке, таком как Javascript, это невозможно, если функция внутри стека не возвращает значение. Веб-страница просто сломается, потому что браузер ничего не сможет сделать. Это не идеально, если мы хотим гибкого пользовательского интерфейса для конечного пользователя. Как мы с этим справимся?

«Параллелизм в JS - одна вещь за раз, кроме действительно асинхронных обратных вызовов»

Самое простое решение - использовать асинхронные обратные вызовы, что означает, что мы запускаем некоторую часть кода и передаем ей обратный вызов (функцию), которая будет выполняться позже. Мы все должны сталкиваться с асинхронными обратными вызовами, как и любой запрос AJAX, использующий $.get(),setTimeout(),setInterval(), Promises, etc. Node, - это все о выполнении асинхронных функций. Все эти асинхронные обратные вызовы не запускаются немедленно и будут запущены через некоторое время, поэтому их нельзя сразу вставить в стек, в отличие от синхронных функций, таких как console.log(), mathematical operations. Куда, черт возьми, они уходят и как с ними обрабатываются?

Если мы видим сетевой запрос в действии в Javascript, как в приведенном выше коде:

  1. Выполняется функция запроса, передающая анонимную функцию в событии onreadystatechange в качестве обратного вызова для выполнения, когда ответ станет доступен когда-нибудь в будущем.
  2. «Вызов скрипта выполнен!» немедленно выводится на консоль
  3. Когда-нибудь в будущем ответ вернется, и наш обратный вызов будет выполнен, выводя его тело на консоль.

Отделение вызывающего абонента от ответа позволяет среде выполнения JavaScript делать другие вещи, ожидая завершения асинхронной операции и запуска их обратных вызовов. 2Вот здесь задействуются API-интерфейсы браузера и вызывают его API-интерфейсы, которые в основном представляют собой потоки, созданные браузером, реализованные на C ++ для обработки асинхронных событий, таких как события DOM, HTTP-запрос, setTimeout и т. Д. (Узнав об этом, в Angular 2, Zones используются, которые обезьяны исправляют эти API, чтобы вызвать обнаружение изменений во время выполнения, что я теперь могу получить, как они смогли этого достичь.)

Веб-API браузера - потоки, созданные браузером, реализованные на C ++ для обработки асинхронных событий, таких как события DOM, HTTP-запрос, setTimeout и т. Д.

Теперь эти WebAPI не могут сами поместить исполняемый код в стек, если бы это было так, то он случайно появился бы в середине вашего кода. Очередь обратного вызова сообщений, описанная выше, показывает путь. 3 Любой из WebAPI помещает обратный вызов в эту очередь, когда он завершает выполнение. Цикл событий теперь отвечает за выполнение этих обратных вызовов в очереди и помещает их в стек, когда он пуст 4. Основная задача цикла обработки событий - смотреть как на стек, так и на очередь задач, помещая в стек первое, что находится в очереди, когда он видит стек как пустой. Каждое сообщение или обратный вызов полностью обрабатываются до обработки любого другого сообщения.

while (queue.waitForMessage()) {
  queue.processNextMessage();
}

В веб-браузерах сообщения добавляются каждый раз, когда происходит событие, и к нему прикреплен прослушиватель событий. Если слушателя нет, событие теряется. Таким образом, щелчок по элементу с обработчиком события щелчка добавит сообщение, как и любое другое событие. Вызов этой функции обратного вызова служит начальным кадром в стеке вызовов, и из-за того, что JavaScript является однопоточным, дальнейший опрос и обработка сообщений останавливаются в ожидании возврата всех вызовов в стеке. Последующие (синхронные) вызовы функций добавляют в стек новые кадры вызовов.

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

Надеюсь, вам это нравится, не стесняйтесь оставлять свои ценные комментарии, чтобы помочь мне стать лучше.