Основы интерфейса: познакомим вас с циклом событий в JavaScript.

JavaScript
Основы интерфейса: познакомим вас с циклом событий в JavaScript.

предисловие

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

В этой статье будут рассмотрены:

  1. Концепция процесса и потока.
  2. Цикл событий в браузере
  3. Чем цикл событий Node отличается от браузеров?

Данная статья является личным подведением итогов после работы.Если есть какие-то ошибки или упущения, прошу обращаться ко мне.Большое спасибо.

Что такое процесс? Что такое нить?

Мы часто говорим, что javascript однопоточный? Так что же такое процесс? Что такое нить?

По сути,И процессы, и потоки — это период работы, распределенный внутри ЦП.

  1. Процесс — это процесс выполнения программы, динамическая концепция и базовая единица для распределения ресурсов и управления ими во время выполнения программы.
  2. Поток (thread) — базовая единица планирования и диспетчеризации ЦП, которую можно комбинировать сотносятся к одному и тому же процессуДругие потоки совместно используют все ресурсы, принадлежащие процессу,это время, необходимое для выполнения инструкции.

Взяв в качестве примера браузер Chrome, можно выделить четыре основных процесса:

  1. Браузерный процесс: отвечает за TAB браузера вперед, назад, адресную строку, панель закладок и обрабатывает некоторые невидимые базовые операции браузера, такие как сетевые запросы и доступ к файлам.
  2. Процесс визуализации: отвечает за работу, связанную с отображением на вкладке, также известную как механизм рендеринга.
  3. Плагин Процесс: отвечает за управление плагинами, используемыми веб-страницами.
  4. Процесс графического процессора: отвечает за обработку задач графического процессора для всего приложения.

Поток движка javaScript работает в процессе рендеринга

Каковы преимущества однопоточности JS

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

преимущество:Память можно сохранить. Время переключения контекста может быть сохранено. Нет проблем с блокировкой.

Хотя javascript является однострочным, в javascript есть понятия синхронизации и асинхронности, что решает проблему блокировки js.

Стек выполнения (что такое стек выполнения)

В то же время нам необходимо понять концепцию стека выполнения, который можно рассматривать какстек выполненияЯвляетсяструктура стека для хранения вызовов функцийКогда мы выполняем JS-код, мы помещаем функции в стек выполнения.

Итак, что вы делаете, когда сталкиваетесь с асинхронным кодом?
На самом деле, когда встречается асинхронный код, он будет приостановлен и добавлен в очередь Task (там много задач), когда его нужно выполнить.

Event Loop

В JavaScript задачи делятся на два типа: одна — MacroTask, также называемая Task, а другая — MicroTask.

микрозадачи: process.nextTick(), обещание, MutationObserver
задача макроса: скрипт, setTimeout, setInterval, setImmediate, ввод-вывод, визуализация пользовательского интерфейса

Думая над вопросом

console.log('script start');

async function async1() {
    await async2();
    console.log('async1 end');
};

async function async2() {
    console.log('async2 end');
};

async1()

setTimeout(() => {
    console.log('setTimeout')
}, 0)

new Promise((resolve, reject) => {
    console.log('promise start');
    resolve()
})
.then(() => console.log('promise end'))

console.log('script end')

Браузер распечатает: начало сценария, конец async2, начало обещания, конец сценария, конец async1, конец обещания, setTimeout

Здесь мы анализируем каждый этап:

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

Выполнить метод async1, т.к. метод после await будет выполнен сразу (его можно рассматривать как исполнителя промиса, который будет выполняться вместе с кодом скрипта), будет напечатан конец asycn2, и будет добавлен код после await в очередь микрозадач. Когда встречается setTimeout, он будет добавлен в очередь задач макроса, продолжит выполнение и распечатает начало промиса напрямую.Добавит разрешенную функцию обратного вызова в очередь микрозадач и, наконец, выполнит конец скрипта.

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

  2. Наконец, очистите очередь задач макроса и распечатайте: setTimeout

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

Таким образом, механизм цикла событий браузера следует следующим шагам:

  1. Сначала выполните глобальный код синхронизации скрипта, который является задачей макроса.
  2. Стек выполнения пуст, проверьте, есть ли какой-либо асинхронный код для выполнения
  3. Выполнить все микрозадачи
  4. После выполнения всех микрозадач страница рендерится при необходимости
  5. Начать следующий раунд Event-loop


Так что, по сути, асинхронность в JS на самом деле синхронна.

Итак, микрозадачи быстрее, чем макрозадачи?

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

В чем разница между циклом событий в узле и в браузере?

Цикл событий в браузере и Node на самом деле отличается.

Цикл событий в узле разделен на 6 шагов:

   ┌───────────────────────┐
┌─>│        timers         │<————— 执行 setTimeout()、setInterval() 的回调
│  └──────────┬────────────┘
|             |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
│  ┌──────────┴────────────┐
│  │     pending callbacks │<————— 执行由上一个 Tick 延迟下来的 I/O 回调(待完善,可忽略)
│  └──────────┬────────────┘
|             |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
│  ┌──────────┴────────────┐
│  │     idle, prepare     │<————— 内部调用(可忽略)
│  └──────────┬────────────┘     
|             |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
|             |                   ┌───────────────┐
│  ┌──────────┴────────────┐      │   incoming:   │ - (执行几乎所有的回调)
│  │         poll          │<─────┤  connections, │ 
│  └──────────┬────────────┘      │   data, etc.  │ 
│             |                   |               | 
|             |                   └───────────────┘
|             |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
|  ┌──────────┴────────────┐      
│  │        check          │<————— setImmediate() 的回调将会在这个阶段执行
│  └──────────┬────────────┘
|             |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
│  ┌──────────┴────────────┐
└──┤    close callbacks    │<————— socket.on('close', ...)
   └───────────────────────┘

Здесь это может показаться немного сложным, но давайте помнить о следующих двух моментах:

  1. В узле микрозадачи должны выполняться перед макрозадачами
  2. Для микрозадач он будет очищать очередь микрозадач перед каждым этапом более шести этапов завершения.

Возьмите каштан для легкого понимания:

fs.readFile(__dirname, () => { // fs.readFile 称为I/O
    setTimeout(() => {
        console.log('setTimeout'); // 2
    }, 0)
    setImmediate(() => {
        console.log('setImmediate'); // 1
    })
});

Давайте проанализируем приведенный выше код: во-первых, fs.readFile соответствует этапу опроса на приведенном выше рисунке, поэтому он продолжит выполнение до этапа проверки и выполнит свою функцию обратного вызова для печати setImmediate в первый раз, а затем запустит таймеры. stage для выполнения обратного вызова setTimeout для печати setTimeout.

порядок выполнения process.nextTick()?

process.nextTick() — это специальный асинхронный API, не принадлежащий ни к одной из фаз цикла событий. На самом деле, когда Node сталкивается с этим API, цикл событий вообще не будет продолжаться, а немедленно остановится для выполнения process.nextTick(), который продолжит цикл событий после этого выполнения.

Опять же, давайте возьмем вопрос в качестве примера:

setTimeout(() => {
 console.log('timer1')
 Promise.resolve().then(function() {
   console.log('promise1')
 })
}, 0)
process.nextTick(() => {
 console.log('nextTick')
 process.nextTick(() => {
   console.log('nextTick')
   process.nextTick(() => {
     console.log('nextTick')
     process.nextTick(() => {
       console.log('nextTick')
     })
   })
 })
})

Анализируются на каждом этапе:

В узле микрозадачи должны выполняться перед макрозадачами, поэтому выполните функцию обратного вызова process.nextTick, чтобы распечатать nextTick, и продолжайте выполнять внутренний обратный вызов process.nextTick, пока очередь не будет очищена, а затем войдите в стадию таймеров, чтобы последовательно распечатать timer1, promise1

разница

В среде браузера и Node время выполнения очереди микрозадач отличается:

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

Суммировать

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