DEV Community

Cover image for ✨♻️ Визуализируя JavaScript. Часть 2/7: цикл событий [перевод]
Mari Kalyuzhna
Mari Kalyuzhna

Posted on

✨♻️ Визуализируя JavaScript. Часть 2/7: цикл событий [перевод]

Данный текст является переводом оригинальной статьи ✨♻️ JavaScript Visualized: Event Loop авторства ****Lydia Hallie. В процессе адаптации на русский язык я попыталась сохранить стиль автора.

Статья является частью серии из 7-ми текстов, в которой Лидия доступно, с живыми иллюстрациями и юмором, раскрывает одни из самых базовых и, вместе с тем, сложных для понимания концепций и принципов JavaScript.

Введение

«О боже, цикл событий» — так наверняка думал хотя бы раз в своей жизни любой frontend–разработчик. Это одна из тех вещей, с которыми так или иначе приходится сталкиваться каждому JavaScript разработчику чуть ли не каждый день, но поначалу концепция Event Loop кажется запутанной. Я визуал и умею наглядно представлять информацию, поэтому решила попытаться помочь вам, объяснив это визуально с помощью гифок с низким разрешением, потому что на дворе 2019 год [на момент написания оригинального поста], а гифки почему-то все еще пиксельные и размытые.

Но прежде всего, что такое цикл событий и почему вас это должно волновать?

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

К счастью, браузер предоставляет нам некоторые функции, которых нет в самом движке JavaScript: Web API. Сюда входят DOM API, setTimeout, HTTP-запросы и т. д. Это может помочь нам создать асинхронное, поведение, которое не будет блокировать основной поток 🚀

Стек вызовов

Когда мы вызываем функцию, она добавляется в нечто, называемое стеком вызовов (call stack). Стек вызовов является частью движка JS и не зависит от браузера. Это очередь, что означает “первый пришёл, последний ушёл” (представьте себе стопку блинов). Когда функция возвращает значение, оно извлекается из стека 👋

Стек вызовов — это структура данных, которая, говоря упрощённо, записывает сведения о месте в программе, где мы находимся. Если мы переходим в функцию, мы помещаем запись о ней в верхнюю часть стека. Когда мы из функции возвращаемся, мы вытаскиваем из стека самый верхний элемент и оказываемся там, откуда вызывали эту функцию. Это — всё, что умеет стек.

1 || Функции помещаются в стек вызовов, когда мы их вызываем, и извлекаются, когда возвращают какой-то результат

1 || Функции помещаются в стек вызовов, когда мы их вызываем, и извлекаются, когда возвращают какой-то результат

Очередь вызовов и setTimeout

Функция respond возвращает setTimeout функцию. setTimeout предоставляется Web API: он позволяет нам откладывать задачи на заданное время, не блокируя основной поток. Функция обратного вызова (коллбэк), которую мы передали функции setTimeout, стрелочная функция () => { return 'Hey'}, добавляется в Web API. Тем временем setTimeout функция и функция respond извлекаются из стека, они обе вернули свои значения!

2 || setTimeout предоставляется нам со стороны браузера, Web API позаботится о коллбэке, который мы передали

2 || setTimeout предоставляется нам со стороны браузера, Web API позаботится о коллбэке, который мы передали

В Web API таймер работает столько, сколько мы указали во втором аргументе, — 1000 мс (по умолчанию время в JS указывается в мс). Коллбэк не добавляется сразу в стек вызовов, вместо этого он откладывается в нечто, называемое очередью.

3 || Когда время таймера закончится (1000 мс, в данном случае), коллбэк будет помещён в очередь вызовов

3 || Когда время таймера закончится (1000 мс, в данном случае), коллбэк будет помещён в очередь вызовов

Это может сбить с толку: окончание таймера не означает, что функция обратного вызова добавляется в стек вызовов (и, следовательно, возвращает значение) через 1000 мс! Коллбэк просто добавляется в очередь через 1000 мс. Но это очередь, функция должна дождаться своей очереди!

Очереди работают по принципу “первый пришёл, первый вышел” (представьте себе очередь в магазине).

Наконец, часть, которую мы все ждали… Пришло время циклу событий выполнить свою единственную задачу: соединить очередь со стеком вызовов! Если стек вызовов пуст, т. е. все ранее вызванные функции вернули свои значения и были извлечены из стека, первый элемент из очереди добавляется в стек вызовов. В этом случае никакие другие функции не вызывались, а это означает, что стек вызовов был пуст к тому моменту, когда функция обратного вызова стала первым элементом в очереди.

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

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

Наконец, коллбэк добавляется в стек вызовов, вызывается, возвращает значение и извлекается из стека. Ура! 🎉

5 || Коллбэк добавляется в стек вызовов и выполняется. После того, как функция возвращает значение, они извлекается из стека вызовов

5 || Коллбэк добавляется в стек вызовов и выполняется. После того, как функция возвращает значение, они извлекается из стека вызовов

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

const foo = () => console.log("First");
const bar = () => setTimeout(() => console.log("Second"), 500);
const baz = () => console.log("Third");

bar();
foo();
baz();
Enter fullscreen mode Exit fullscreen mode

Разобрались? Давайте быстро взглянем, что происходит, “за кулисами” когда мы запускаем этот код в браузере:

Image description

  1. Мы вызываем bar.bar возвращает таймер setTimeout.
  2. Коллбэк, который мы передали в setTimeout, добавляется в Web API, функции setTimeout и bar извлекаются из стека вызовов.
  3. Таймер запускается, тем временем foo вызывается и логирует First. foo возвращает *undefined*.
  4. Вызывается baz и попадает в стек вызовов, тем временем, коллбэк таймера добавляется в очередь.
  5. baz логирует Third. Цикл событий видит, что стек вызовов пустеет после того, как из baz возвращается *undefined*. Теперь коллбэк таймера добавляется в стек вызовов.
  6. Наконец, логируется Second. Коллбэк отработал, вернул *undefined* и вышел из стека вызовов.

Заключение

Надеюсь, что благодаря этому посту вы почувствуете себя более комфортно с циклом событий! Не волнуйтесь, если это все еще кажется запутанным, самое главное — понять, откуда могут возникнуть определенные ошибки/поведение, чтобы эффективно находить в Google нужные термины и, в конечном итоге, оказаться на правильной странице Stack Overflow 💪🏼 Не стесняйтесь обращаться к автору, если у вас есть вопросы!



Lydia Hallie в соцсетях: Twitter || Instagram || GitHub || LinkedIn || Website

P.S. Автор использовала Keynote для создания анимаций и записей экрана.

P.P.S. В других статьях будут рассмотрены такие штуки, как Поднятие (hoisting), область действия (scope chain), прототипное наследование (prototypal inheritance), генераторы и итераторы (generators ans iterators), промисы (promises) и async/await.

Sentry blog image

How to reduce TTFB

In the past few years in the web dev world, we’ve seen a significant push towards rendering our websites on the server. Doing so is better for SEO and performs better on low-powered devices, but one thing we had to sacrifice is TTFB.

In this article, we’ll see how we can identify what makes our TTFB high so we can fix it.

Read more

Top comments (0)

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay