- Оригинальный автор:Gaurav Pandvia
- Оригинальная ссылка:medium.com/@G aura V. Тарелка…
- Для некоторых ссылок в тексте может потребоваться лестница.
- Критика и исправления приветствуются.
Сегодня веб-разработчики (мы предпочитаем, чтобы их называли фронтенд-инженерами) могут делать все, что угодно, используя язык сценариев, от обеспечения взаимодействия в браузере до разработки компьютерных игр, настольных инструментов, кроссплатформенных мобильных приложений и даже развертывания на стороне сервера. (например, самый популярный Node.js) для подключения к любой базе данных. Поэтому важно понимать внутренности Javascript, чтобы его можно было использовать оптимально и эффективно. Это также является целью данной статьи.
Экосистема Javascript становится все более сложной. Для создания современного веб-приложения неизбежно использование Webpack, Babel, ESLint, Mocha, Karma, Grunt... Что мне использовать? Для чего они? Я нашел этот комикс, который прекрасно иллюстрирует бедственное положение современных веб-разработчиков:
Прежде чем погрузиться с головой в море фреймворков и библиотек, каждый разработчик Javascript должен сначала понять, как реализован Javascript под капотом. Почти каждый JS-разработчик слышал термин «V8», но некоторые могут понятия не иметь, что это слово означает и почему оно используется. В первый год моей карьеры в сфере профессионального развития я очень мало знал об этих причудливых терминах и больше заботился о том, чтобы сначала выполнить работу. Но это не удовлетворяет моего любопытства, мне интересно, как Javascript может все это делать. Я решил копнуть немного глубже, погуглил и нашел несколько отличных блогов, в том числеPhilip Robertsизa great talk at JSConf on the event loop. Поэтому я решил обобщить свой опыт обучения и поделиться им. Так как нужно знать так много, я разделил эту статью на две части. В этой части вводятся общие термины, а во второй части объясняется, как эти термины связаны.
Javascript — это однопоточный одновременный язык, что означает, что он может обрабатывать только одну задачу за раз и выполнять один фрагмент кода. Его стек вызовов вместе с кучей и очередью составляютМодель параллелизма Javascript(реализовано в V8). Давайте рассмотрим эти слова одно за другим.
- Стек вызовов: это структура данных, которая записывает функции, которые мы вызываем в программе. Если мы вызываем функцию для выполнения, мы помещаем какую-то запись в верхнюю часть стека вызовов; когда мы возвращаемся из функции, мы извлекаем запись из вершины стека вызовов.
Когда мы запускаем код на изображении выше, мы сначала ищем начало всего выполнения — основную функцию. В приведенном выше примере последовательность выполнения начинается сconsole.log(bar(6)), то на этот раз выполнение помещается в стек вызовов, а слоем над ним является функцияbarи ее параметры, функцияbarВызовите функциюfoo,fooтакже помещается в стек; иfooнемедленноreturnимеет значение, поэтому выталкивается из стека вызовов; аналогично,barто всплывает, наконецconsoleОператор напечатал результат и вытолкнул его. Все эти действия, одно за другим, произошли в мгновение ока.
Вы, должно быть, видели этот длинный и красный стек ошибок в консоли браузера.Он просто показывает текущее состояние стека вызовов и место сообщения об ошибке в функции в виде стека сверху вниз (см. следующий рисунок). .
Иногда, когда мы вызываем функцию несколько раз в рекурсивной форме, она попадет в бесконечный цикл, а для браузера Chrome его ограничение на размер стека вызовов составляет 16000 слоев, при превышении лимита программа будет прекращено и выброшеноошибка достижения предела стека(Смотри ниже).
- куча: Объект будет размещен в куче — свободной структуре в памяти. Все выделения памяти для переменных и объектов происходят в куче.
- очередь: среда выполнения Javascript, которая содержит очередь сообщений, которая представляет собой серию сообщений, которые необходимо обработать, и связанные с ними функции обратного вызова, которые необходимо выполнить. Когда в стеке вызовов достаточно места, сообщение берется из очереди и обрабатывается, что вызывает связанную функцию (и, таким образом, генерирует слой стека инициализации). Когда стек снова очищается, обработка сообщения заканчивается. Проще говоря, эти сообщения ставятся в очередь, а функции обратного вызова задаются для реагирования на внешние асинхронные события (такие как щелчки мыши или ответы на HTTP-запросы). Например, если пользователь нажимает кнопку без соответствующей функции обратного вызова, сообщение не будет помещено в очередь.
цикл событий
Когда мы оцениваем производительность кода JS, нам нужно знать, что функции в стеке вызовов сделают программу быстрее или медленнее,console.log()будет быстро, но сforилиwhileИтерация в тысячи раз будет медленнее и будет держать стек вызовов занятым и заблокированным. это называетсяблокирующий скрипт, вы можете быть вWebpage Speed Insightsвидел в.
Сетевые запросы будут медленными, а запросы изображений будут медленными, но, к счастью, служебные запросы можно выполнять с помощью асинхронных функций, таких как AJAX. Что, если бы эти сетевые запросы выполнялись с помощью синхронных функций? Сетевой запрос отправляется на сервер — сервер — это просто какая-то машина где-то, теперь предположим, что сервер может медленно возвращать ответ, в этот момент, если я нажму какую-нибудь кнопку CTA (призыв к действию), или что-то другое, что нужно сделать Рендеринг, ничего не происходит, потому что стек вызовов все еще заблокирован предыдущими сетевыми запросами. В многопоточных языках, таких как Ruby, эту ситуацию можно контролировать, но в однопоточных языках, таких как Javascript, если функция в стеке вызовов не возвращает значение, она всегда блокируется. Браузер ничего не делает и страница вылетает. Таким образом, мы не можем обеспечить плавный пользовательский интерфейс для конечного пользователя. Так что же нам делать?
«Параллелизм в JS — делайте одно за раз, кроме асинхронных обратных вызовов»
Самым ранним решением было использование асинхронных обратных вызовов, что означало, что мы добавляли обратный вызов к некоторой части кода, который выполнялся бы после выполнения кода. Наверное, все мы сталкивались с чем-то вроде AJAX-запросов.$.get(),setTimeout(),setInterval(),Promisesасинхронный обратный вызов. Узел основан на асинхронном выполнении функций. Все эти асинхронные обратные вызовы не будут похожи наconsole.log()Подождите, пока синхронная функция запустится немедленно, но в какой-то момент позже, чтобы она не была помещена в стек вызовов немедленно. Так куда они пошли? Как их контролировать?
Как и в приведенном выше примере, если сетевой запрос сделан в Javascript:
1. 请求函数被执行,给`onreadystatechange`事件传一个匿名函数作为回调,用来在将来响应就绪的时候执行。
2. “Script call done!”立刻输出到控制台。
3. 后续某时刻,响应被返回,回调被执行,响应体被输出到控制台。
Раздельный вызов ответа позволяет среде выполнения Javascript выполнять другие действия, ожидая завершения асинхронной операции и разворачивая выполнение обратного вызова. Браузер подключается и вызывает свой API, который представляет собой API, реализованный на C++ для создания потоков для управления асинхронными событиями, такими как события DOM, HTTP-запросы, setTimeout и т. д.
Эти веб-интерфейсы не могут сами помещать исполняемый код в стек вызовов, а если бы и могли, то интерфейс отображался бы в вашем коде случайным образом (порядок выполнения не контролируется). Обсужденная выше очередь обратного вызова сообщений иллюстрирует это. Любой веб-интерфейс поместит обратные вызовы в эту очередь после завершения выполнения.цикл событийНа этом этапе он отвечает за управление выполнением обратных вызовов в очереди и помещение обратных вызовов в стек, когда стек пуст. Основная работа цикла обработки событий заключается в наблюдении за стеком вызовов и очередью задач, и когда он видит, что стек пуст, он помещает в стек первую задачу в очереди. Каждое сообщение или обратный вызов начинает обрабатываться после обработки предыдущей задачи.
while (queue.waitForMessage()) {
queue.processNextMessage();
}
В веб-браузере, как только событие происходит и связывает прослушиватель событий, сообщение немедленно добавляется в очередь. Если обработчика нет, значит, событие потеряно. Так что нажмите на обработчик событий щелчка привязки, добавьте сообщение и другие события. Вызов обратного вызова будет начальным слоем в стеке вызовов, а поскольку JavaScript является однопоточным, все вызовы в стеке вызововreturnРанее опрос и обработка последующих сообщений были приостановлены. Последующие (синхронизированные) вызовы функций добавляют новые слои в стек вызова.
В следующей части я покажу выполнение кода описанного выше процесса с анимацией, подробно объясняя, что представляют собой различные типы асинхронных функций, кто выполняется первым в очереди, а также приемы для таких функций, как нулевая задержка.