Данный текст является переводом оригинальной статьи ✨♻️ 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 || Функции помещаются в стек вызовов, когда мы их вызываем, и извлекаются, когда возвращают какой-то результат
Очередь вызовов и setTimeout
Функция respond
возвращает setTimeout
функцию. setTimeout
предоставляется Web API: он позволяет нам откладывать задачи на заданное время, не блокируя основной поток. Функция обратного вызова (коллбэк), которую мы передали функции setTimeout
, стрелочная функция () => { return 'Hey'}
, добавляется в Web API. Тем временем setTimeout
функция и функция respond
извлекаются из стека, они обе вернули свои значения!
2 || setTimeout предоставляется нам со стороны браузера, Web API позаботится о коллбэке, который мы передали
В Web API таймер работает столько, сколько мы указали во втором аргументе, — 1000 мс (по умолчанию время в JS указывается в мс). Коллбэк не добавляется сразу в стек вызовов, вместо этого он откладывается в нечто, называемое очередью.
3 || Когда время таймера закончится (1000 мс, в данном случае), коллбэк будет помещён в очередь вызовов
Это может сбить с толку: окончание таймера не означает, что функция обратного вызова добавляется в стек вызовов (и, следовательно, возвращает значение) через 1000 мс! Коллбэк просто добавляется в очередь через 1000 мс. Но это очередь, функция должна дождаться своей очереди!
Очереди работают по принципу “первый пришёл, первый вышел” (представьте себе очередь в магазине).
Наконец, часть, которую мы все ждали… Пришло время циклу событий выполнить свою единственную задачу: соединить очередь со стеком вызовов! Если стек вызовов пуст, т. е. все ранее вызванные функции вернули свои значения и были извлечены из стека, первый элемент из очереди добавляется в стек вызовов. В этом случае никакие другие функции не вызывались, а это означает, что стек вызовов был пуст к тому моменту, когда функция обратного вызова стала первым элементом в очереди.
4 || Цикл событий смотрит на очередь коллбэков и на стек вызовов. Есть стек вызовов пуст, в него перемещается первый элемент из очереди коллбэков
Наконец, коллбэк добавляется в стек вызовов, вызывается, возвращает значение и извлекается из стека. Ура! 🎉
5 || Коллбэк добавляется в стек вызовов и выполняется. После того, как функция возвращает значение, они извлекается из стека вызовов
Читать статью весело, но вам станет комфортно со стеком вызовов только тогда, когда будете работать с ним снова и снова. Попытайтесь выяснить, что выведется в консоль, если мы запустим следующей код:
const foo = () => console.log("First");
const bar = () => setTimeout(() => console.log("Second"), 500);
const baz = () => console.log("Third");
bar();
foo();
baz();
Разобрались? Давайте быстро взглянем, что происходит, “за кулисами” когда мы запускаем этот код в браузере:
- Мы вызываем
bar
.bar
возвращает таймерsetTimeout
. - Коллбэк, который мы передали в
setTimeout
, добавляется в Web API, функцииsetTimeout
иbar
извлекаются из стека вызовов. - Таймер запускается, тем временем
foo
вызывается и логируетFirst
.foo
возвращает*undefined*
. - Вызывается
baz
и попадает в стек вызовов, тем временем, коллбэк таймера добавляется в очередь. -
baz
логируетThird
. Цикл событий видит, что стек вызовов пустеет после того, как изbaz
возвращается*undefined*
. Теперь коллбэк таймера добавляется в стек вызовов. - Наконец, логируется
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.
Top comments (0)