Рубрики
Без рубрики

Запустить, javaScript, запустить

Преамбула давайте признаем. JavaScript – не самый предсказуемый язык. Это может … с меткой JavaScript, WebDev, Web, Frontend.

Преамбула

Давайте признаем. JavaScript – не самый предсказуемый язык. Это может стать довольно странно очень легко. Давайте посмотрим на следующий пример.

setTimeout(() => console.log("1. timeout"));
console.log("2. console");
Promise.resolve("3. promise").then((res) => console.log(res));

// prints
// 2. console
// 3. promise
// 1. timeout

Даже если мы изменим порядок инструкций, это не повлияет на конечный результат 🤨

Promise.resolve("1. promise").then((res) => console.log(res));
setTimeout(() => console.log("2. timeout"));
console.log("3. console");

// prints
// 3. console
// 1. promise
// 2. timeout

Неважно, как мы перетасоваем эти три строки, они всегда будут выполнены в одном и том же порядке Консоль, обещание, тайм -аут 😐

Почему? Ну ты знаешь…

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

Мы собираемся сосредоточиться на веб -браузере JavaScript, тем не менее, большинство вещей, которые мы собираемся обсудить, могут быть связаны с другими агентами, такими как Nodejs.

Стоит отметить

setTimeout (() => {}) равен призыву settimeout (() => {}, 0) . Хотя ни один из них не будет гарантировать немедленное выполнение, поскольку значение тайм -аута ( 0 ) используется для установки минимального периода ожидания, а не точного периода. В любом случае пример выше полностью законен в данном контексте.

Одна вещь за раз

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

Один вещание по номеру вызовов за раз Пауза здесь на секунду … Одна вещь за раз …

Даже когда кажется, что несколько вещей происходят одновременно, в действительности, есть только одна задача, которая выполняется в каждый заданный момент, просто очень быстро.

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

А как насчет консоли JavaScript, то, что мы все используем в инструментах Dev Browser? Это зависит, но, скорее всего, это будет другой процесс, отсюда и другой поток.

❗exception …

Однако «единственный поток» – это поведение по умолчанию, однако мы можем разветвляться из основного потока и запустить наш код JavaScript в отдельном потоке с помощью Веб -работники API .

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

То, что управляет вашим JavaScript

Часто условия выполнения JavaScript и двигатель JavaScript используются взаимозаменяемо. Тем не менее, они похожи на соль 🧂 и зеленый 🟩. Две совершенно разные вещи. Позвольте мне объяснить, что я имею в виду.

Три основных произведения составляют время выполнения JavaScript. Они концептуально разделены. И, скорее всего, разработанный разными людьми/командами/компаниями, и представляют собой независимые части программного обеспечения. Однако они работают в тесном сотрудничестве.

  • JavaScript Engine : Компилирование, оптимизирует и выполняет код, обрабатывает распределение памяти и сбор мусора
  • Петля события : Оркестр и распределяет работу, обеспечивает асинхронность.
  • Browser Web API : Позволяет общаться с вещами, расположенными за пределами времени выполнения (например, таймеры системы, файловая система, HTTP, адресная панель, DOM и т. Д.)

Большая картинка

Двигатель

Двигатель JavaScript … не запускает JavaScript … Он запускает Ecmascript. Разве это не то же самое? Появляется нет, я объясню.

Если мы рассмотрим исходный код произвольного двигателя JavaScript (вы знаете, потому что это случайная вещь, которую мы делаем LOL 🤪), мы найдем реализацию Объявление Ecmascript Анкет Это будет включать все виды базовых объектов (включая объект ), такие как Дата и Строка , ключевые языковые конструкции, такие как петли, условия и так далее. Однако, если мы будем искать скажем SetTimer или принести , мы не найдем много. Потому что они не являются частью Ecmascript. Они являются частью Browser Web API (На самом деле ничего общего с самим веб -сайтом, больше похоже на API браузера 🙃, но вы обнаружите, что это происходит под веб -API, веб -браузером API, Browser API и просто API).

Двигатель JavaScript будет управлять памятью и управлять выполнением нашего сказочного кода. Который никогда не будет выполнен в своей первоначальной форме, двигатель будет постоянно изменять его. Большинство двигателей довольно умны, они будут продолжать оптимизировать код в течение всего срока службы страницы в постоянной погоне за улучшением производительности.

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

В предыдущей статье Анатомия веб -браузера Мы уже обсуждали некоторые из ключевых аспектов двигателя JavaScript (анализ, предварительная обработка, компиляция и оптимизация/де-оптимизация). С более глубоким акцентом на V8 Компиляционный трубопровод. Статья больше сосредоточена на самой обработке кода и слегка касается двигателя браузера (не путать с двигателем JavaScript) и основных концепциях рендеринга, поэтому, если это звучит интересно, не забудьте проверить его после. 😏

Петля

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

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

while (true) {
  if (allDone()) {
    const thingsToDo = getThingsToDo();
    doThings(thingsToDo);
  }
}

На каждой итерации цикл событий выполняет упорядоченную серию заданий, определенных в Обработка модели документация Анкет Мы вернемся к нему в течение статьи.

Циклы событий и петли мероприятий

Цикл событий, на который мы обычно называем в контексте веб -браузера, является Цикл оконного события Анкет Каждый Происхождение получит один. Однако иногда мало вкладок/окон из того же происхождения могут поделиться одним циклом. Особенно, когда одна вкладка открывается с другой. (Вот где мы можем использовать несколько вкладок/страниц одновременно)

Во всяком случае, Цикл оконного события это не единственный цикл событий, работающий в браузере. Веб -работники (и другие работники) будут использовать свои собственные Рабочий цикл событий Анкет Иногда это будет разделено на всех работников. И рабочие будет иметь свои собственные Процетка рабочего события Анкет

Но в дальнейшем, когда мы ссылаемся на цикл событий, мы действительно будем ссылаться на Цикл оконного события Анкет

Задачи, микротаски и макротаски

Учитывая однопоточную природу языка, трудно переоценить важность асинхронности. Асинхронное поведение реализуется набором очередей (FIFO).

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

Есть два (не три …) основные типы очередей, очередь задач и очередь микротаски. На первый взгляд, это может выглядеть так, как будто они идентичны. И это верно в некоторой степени, они играют ту же роль: отложить выполнение кода на потом. Разница заключается в том, как цикл событий использует их.

Вы, наверное, задаетесь вопросом, куда пошли макротаски … Macrotask – это просто имя V8 для задачи. Таким образом, после этого мы будем использовать термин -задачу, и все, что мы говорим для задачи, может быть применено к Macrotask

Очередь задач

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

Часто наш код выглядит так

do this on button click
do that when the server responds
call the server

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

Очередь вне нашего прямого досягаемости. Dequeueing происходит в цикле событий. Большинство задач проведены с помощью так называемых общих источников задач. Это включает в себя Пользовательские взаимодействия , DOM Манипуляция , сетевая деятельность и История Анкет Хотя у нас, очевидно, есть способ повлиять на то, что и когда попадет в очередь задач (например, через обработку событий).

Хорошо, это будет тяжелое предложение, так что терпите меня здесь … Процесс удаления, происходящий один раз на итерацию, и он, по крайней мере, будет (сохранить декеинг) до тех пор, пока самая новая задача из предыдущей итерации (которая была в очереди в момент начала итерации) все еще не будет в очереди. Имейте в виду, что самые новые задачи будут в хвосте очереди, из -за концепции FIFO (сначала сначала). Другими словами, все новые задачи, которые мы добавляем, будут выполняться в следующей итерации, все текущие/старые задачи будут выполняться в этой итерации. В соответствии с Обработка модели документация Анкет

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

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

Микротаска очередь

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

  1. Микротаски обрабатываются на разных этапах на итерации цикла событий. Мы упомянули выше, что каждая итерация цикла событий после строгого порядка, известного как обработка модели ;
  2. Микротаски могут запланировать другие микротаски, и новая итерация цикла событий не начнется, пока мы не достигнем конца очереди;
  3. Мы можем напрямую включить микрозабочку с queemicrotask ;

Остальное в значительной степени одинаково, после того, как задача будет выполнена, и обратный обратный вызов извлечен, оно будет перемещено в стек вызовов для немедленного выполнения.

Browser Web API

Последняя часть в головоломке – API, API браузера. Мост соединения между кодом и всем за пределами времени выполнения.

Связь с файловой системой или удаленными сервисными вызовами. Различные подписки на мероприятие. Взаимодействие с адресной панелью и историей. И более. Облегчается Browser API.

Browser API позволяет нам определять обработчики событий. И это самый распространенный способ для разработчиков передавать обратные вызовы (обработчики событий) в очередь задач.

Browser API являются специфичными для браузера. Каждый браузер реализует их отдельно. Следовательно, они работают по -разному, хотя, вероятно, будут иметь такой же эффект. Следовательно, время от времени вы можете наткнуться на новую крутую функцию, которая не поддерживается Internet Explorer Браузер X. и самая распространенная причина, API не реализован в браузере X.

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

Я до сих пор помню 👴 Как вам пришлось реализовать звонки AJAX (xmlhttprequest) для всех возможных браузеров в вашем коде до тех пор, пока а jQuery появился. Это был изменение игры.

Собрать вещи вместе

Мы обсуждали довольно много вещей до сих пор. Давайте собраем их всех в один список. И пройдите в том же порядке, что и цикл событий.

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

Браузер найдет немного JavaScript либо между <Скрипт> теги или в консоли Devtools. И в конечном итоге это подтолкнет его к очереди задачи …

  1. Цикл продолжает проверять очередь задач. Как только он найдет начальный код, цикл перенесет его в стек вызовов. Двигатель немедленно вступает во владение и выполняет свою работу, пока он не опустошит стек вызовов.
  2. Цикл проверит очередь (ы) микрозамачивания. Он будет держать задачи по поводу очереди в очереди и толкать их (по одному элементу за раз) к стеку вызовов (и он будет продолжать выполнять до пустых) из очереди микротаски до тех пор, пока очередь из микрозабочки не станет пустой. Помните, что код Microtask может выдвигать еще одну микрозабочку в очередь, и он будет выполнен во время той же итерации (прямо здесь).
  3. Как стек двигателя, так и очередь микротаски теперь пусты.
  4. Наконец, цикл возвращается в очередь задач. Имейте в виду, что события все время излучали, либо в коде, либо за его пределами. Цикл отметит новейшую задачу (таковую в хвосте очереди) в очереди и начнет выполнять задачи от самого старого до самого новейшего (головы к хвосту) и выдвигать код в стек двигателя, пока не достигнет отмеченной задачи.
  5. Затем он сделает некоторые другие, не связанные с работой во время выполнения, например, рендеринг.
  6. Как только все будет сделано, новая итерация начинается с точки 1

Пример

Давайте вернемся к примеру с начала статьи …

setTimeout(() => console.log("1. timeout"));
console.log("2. console");
Promise.resolve("3. promise").then((res) => console.log(res));

// prints
// 2. console
// 3. promise
// 1. timeout

Не имеет значения, как мы бы перетасовали инструкции, полученный результат останется прежним

На самом деле теперь это имеет гораздо больше смысла, проверьте это.

  • Во -первых, весь этот код отправляется в стек вызовов и выполняется последовательно.
    • settimeout Почти сразу же отправляет обратный вызов в очередь задач.
    • Консоль.log Отпечатает строку в консоли (это наша первая строка 2. Консоль )
    • Обещание.resolve (...). Затем (...) немедленно разрешенное обещание, таким образом, он отправляет обратный вызов в очередь Микротаски в тот же момент, который он выполняется.
  • Stack завершает выполнение, он пуст и передает управление обратно в цикл события.
  • Цикл событий проверяет очередь в микрозамашке и находит там обратный вызов от разрешенного обещания и отправляет его в стек вызовов (это наша вторая строка 3. Обещание )
  • Очередь микротаски пуста, стек вызовов пуст, теперь это поворот очереди задач.
  • Петля событий находит обратный вызов тайм -аута в очереди задачи и отправляет его в стек вызовов (это наша третья и последняя строка 1. Тайм -аут )

И мы закончили, стек пуст вместе со всеми очередями. Это было не так уж плохо, не так ли?

Рекурсионные примеры

Хорошо, пришло время повеселиться! 🤓 Учитывая, что мы уже знаем, как взаимодействовать и чего ожидать от очередей и стека. Мы постараемся реализовать три различных примера бесконечной рецирсии. Каждый будет использовать один данный механизм.

Это будет веселее, если вы откроете консоль и попытаетесь запустить примеры кода самостоятельно. Только не используйте консоль этой страницы, лол. Я также посоветовал подготовить менеджер задач браузера, чтобы следить за изменениями в памяти и потреблении процессора. Большинство современных браузеров будут иметь один в условиях.

Давайте начнем с классики.

Стек вызовов

const recursive = () => {
  console.log("stack");
  recursive();

  console.log("unreachable code");
};

recursive();

console.log("unreachable code");

/*
stack
stack
stack
...

Uncaught RangeError: Maximum call stack size exceeded
    at recursive (:2:1)
    at recursive (:3:1)
    at recursive (:3:1)
    at recursive (:3:1)
    at recursive (:3:1)
    at recursive (:3:1)
    at recursive (:3:1)
    at recursive (:3:1)
    at recursive (:3:1)
    at recursive (:3:1)
*/

Бесконечная рекурсия и его старый старый приятель переполняют исключение. Бьюсь об заклад, вы видели несколько из них раньше … Исключение переполнения стека заключается в достижении максимального размера стека вызовов. Как только мы превышаем максимальный размер, он взорвется с Максимальный размер стека вызовов превышен Анкет

Обратите внимание, что есть несколько Консоль.log Это никогда не будет напечатано. Помните, что каждый раз, когда мы выдвигаем новый элемент в стеке вызовов, двигатель немедленно переключается на него, поскольку мы просто нажимаем на новые элементы и никогда не появляемся. Стек продолжает расти, пока мы не достигнем его максимума …

Очередь задач

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

const recursiveTask = () => {
  console.log("task queue");
  setTimeout(recursiveTask);

  console.log("reachable code 1");
};

recursiveTask();

console.log("reachable code 2");

/*
reachable code 2
task queue
reachable code 1
task queue
reachable code 1
task queue
reachable code 1
task queue
reachable code 1
...
*/

Обратите внимание, что оба дополнительных Консоль.log Заявления напечатаны. Поскольку все время мы добавляем новую задачу в очередь задач, мы добавляем ее для следующей итерации, а не для немедленного выполнения. Следовательно, весь код в этом примере обрабатывается перед началом новой итерации. Следите за следами памяти. Он будет расти довольно быстро вместе с использованием процессора. За минуту моя вкладка прошла 1 концерт памяти.

Микротаска очередь

ОК, последний, мы сделаем то же самое, бесконечную рекурсию, но на этот раз для очереди микротаски.

const recursiveMicrotask = () => {
  console.log("microtask queue");
  queueMicrotask(recursiveMicrotask);

  console.log("reachable code 1");
  setTimeout(() => console.log("unreachable code 1"));
};

recursiveMicrotask();

console.log("reachable code 2");
setTimeout(() => console.log("unreachable code 2"));

/*
reachable code 2
microtask queue
reachable code 1
microtask queue
reachable code 1
microtask queue
reachable code 1
microtask queue
reachable code 1
...
*/

Обратите внимание, как задачи из очереди задач никогда не выполняются («недоступный код»). Это происходит потому, что мы никогда не заканчиваем итерацией в текущей цикле событий, мы продолжаем добавлять микротаски в очередь микротаски, и это предотвращает завершение итерации. Если вы оставите его достаточно долго, вы заметите, что страница (включая адресную строку) становится менее отзывчивой. Пока он полностью не умрет. Конечно, следы памяти (и использование процессора) будет продолжать расти намного быстрее, поскольку мы загрязняем очередь задач, но если мы удалим оба settimeout Это снизит темпы роста следа памяти.

📝 Примечание

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

Но вот небольшой пример бесконечного генератора чисел, который показывает его суть.

function* generateNumber() {
  let i = 0;

  while (true) yield i++;
}

const numbers = generateNumbers();

console.log(numbers.next().value); // 0
console.log(numbers.next().value); // 1
console.log(numbers.next().value); // 2

Вот и все.

Конечно, все, что мы смотрели, является упрощенным представлением. Тем не менее, он в достаточных количествах подробно иллюстрирует, как функционирует время выполнения. Он достаточно точен, чтобы объяснить истинную природу асинхронности и последовательностей выполнения кода в JavaScript. А также, как мы надеемся, раскрывают какое -то «странное» поведение и «неожиданные» условия гонки.

JavaScript имеет чрезвычайно низкий входной барьер. И часто это путается с нестабильностью. Тем не менее, некоторые из его поведения являются каким-то компромиссом и оплатой за такой низкий входной барьер. Хотя там осталось мало ошибок для обратной совместимости, лол …

Если вам понравилось чтение, не забудьте проверить другую связанную статью Анатомия веб -браузера Анкет

👋

Оригинал: “https://dev.to/vudodov/run-javascript-run-3lf4”