Как быстро начать работу с VUE3.0: узнайте
о контексте выполнения, стеке выполнения и механизме выполнения (синхронные задачи, асинхронные задачи, микрозадачи, макрозадачи и циклы событий) в js
Это частая контрольная точка на собеседованиях. Некоторые друзья могут вас смутить, когда вас об этом спросят, поэтому я подведу итог сегодня, надеясь, что это поможет вам перед экраном.
Прежде чем говорить о контексте выполнения и механизме js
js
давайте поговорим о потоках и процессах.
Официально线程
— это наименьшая единица планирования CPU
.
? Официально进程
— это наименьшая единица распределения ресурсов CPU
.
线程
— это модуль выполнения программы, основанный на进程
. С точки зрения непрофессионала,线程
— это поток выполнения в программе.进程
может иметь один или несколько线程
.
В进程
существует только один поток выполнения, называемый单线程
. То есть, когда программа выполняется, пути программы располагаются в последовательном порядке. Предыдущие должны быть обработаны, прежде чем будут выполнены последующие.
Несколько потоков выполнения в进程
называются多线程
, то есть в программе могут одновременно выполняться несколько разных线程
для выполнения разных задач. Это означает, что одной программе разрешено создавать несколько параллельных线程
выполнения для выполнения соответствующих задач. .
Ниже автор приведёт простой пример. Например, если мы открываем qq音乐
и слушаем песни, qq音乐
можно понимать как процесс. В qq音乐
мы можем скачивать во время прослушивания песен. песни — это поток, а загрузка — это процесс. Если мы снова откроем vscode
для написания кода, это будет другой процесс.
Процессы независимы друг от друга, но некоторые ресурсы распределяются между потоками одного процесса.
Жизненный цикл потока проходит пять стадий.
Новое состояние: после использования ключевого слова new
и класса Thread
или его подкласса для создания объекта потока объект потока находится в новом состоянии. Он остается в этом состоянии до тех пор, пока программа start()
поток.
Состояние готовности: когда объект потока вызывает метод start()
, поток переходит в состояние готовности. Поток в состоянии готовности находится в очереди готовности и может быть запущен немедленно, пока он получает право на использование CPU
.
Состояние выполнения: если поток в состоянии готовности получает ресурсы CPU
, он может выполнить run()
, и поток находится в рабочем состоянии. Поток в рабочем состоянии является самым сложным, он может стать заблокированным, готовым и мертвым.
Состояние блокировки: если поток выполняет sleep(睡眠)
, suspend(挂起)
, wait(等待)
и другие методы, после потери занятых ресурсов поток перейдет в состояние блокировки из рабочего состояния. В состояние готовности можно снова войти после истечения времени ожидания или получения ресурсов устройства. Его можно разделить на три типа:
Блокировка ожидания: поток в состоянии выполнения выполняет метод wait()
, заставляя поток войти в состояние блокировки ожидания.
Синхронная блокировка: потоку не удается получить synchronized
блокировку синхронизации (поскольку блокировка синхронизации занята другими потоками).
Другая блокировка: когда запрос I/O
выдается путем вызова sleep()
или join()
потока, поток переходит в состояние блокировки. Когда время ожидания состояния sleep()
истекает, join()
ожидает завершения потока или истечения времени ожидания или завершения обработки I/O
, и поток возвращается в состояние готовности.
Состояние смерти: когда работающий поток завершает свою задачу или возникают другие условия завершения, поток переключается в состояние завершения.
JS
Как язык сценариев браузера JS
в основном используется для взаимодействия с пользователями и управления DOM
. Это определяет, что он может быть только однопоточным, иначе это вызовет очень сложные проблемы с синхронизацией. Например, предположим, что JavaScript
одновременно есть два потока. Один поток добавляет контент в определенный узел DOM
, а другой поток удаляет этот узел. В этом случае какой поток должен использовать браузер?
Когда движок JS
анализирует фрагмент исполняемого кода (обычно этап вызова функции), он сначала выполняет некоторую подготовительную работу перед выполнением. (Контекст выполнения (называемый EC
)» или его также можно назвать средой выполнения .
В javascript
существует три типа контекста выполнения:
глобальный контекст выполнения . Это стандартный или самый базовый контекст выполнения. В программе будет только один глобальный контекст, и он будет существовать на протяжении всего жизненного цикла. Сценарий javascript
нижняя часть стека выполнения не будет уничтожена при извлечении стека. Глобальный контекст создаст глобальный объект (возьмем, к примеру, среду браузера, этим глобальным объектом является window
) и привяжет значение this
к этому глобальному объекту.
Контекст выполнения функции При каждом вызове функции создается новый контекст выполнения функции (независимо от того, вызывается ли функция повторно).
Контекст выполнения функции Eval. Код, выполняемый внутри функции eval
, также будет иметь собственный контекст выполнения, но поскольку eval
используется нечасто, он здесь анализироваться не будет.
Ранее мы упоминали, что js
создает контекст выполнения во время работы, но контекст выполнения необходимо сохранить, так что же используется для его хранения? Вам нужно использовать структуру данных стека.
Стек представляет собой структуру данных в порядке очереди.
Таким образом, контекст выполнения, используемый для хранения контекста выполнения, созданного во время выполнения кода, представляет собой стек выполнения .
При выполнении фрагмента кода JS
Затем движок JS
создаст глобальный контекст выполнения и push
в стек выполнения. В этом процессе движок JS
выделит память для всех переменных в этом коде и присвоит начальное значение (неопределенное). JS
движок вступит в стадию выполнения, в этом процессе JS
движок будет выполнять код построчно, то есть присваивать значения (реальные значения) переменным, которым была выделена память одна за другой.
Если в этом коде есть вызов function
, механизм JS
создаст контекст выполнения функции и push
в стек выполнения. Процесс создания и выполнения аналогичен глобальному контексту выполнения.
Когда стек выполнения завершен, контекст выполнения будет извлечен из стека, а затем будет введен следующий контекст выполнения.
Позвольте мне привести пример ниже. Если в нашей программе есть следующий код:
console.log("Global Execution Context start"); функция первая() { console.log("первая функция"); второй(); console.log("Опять первая функция"); } функция секунда() { console.log("вторая функция"); } первый(); console.log("Глобальный контекст выполнения end");
Давайте кратко проанализируем приведенный выше пример.
Сначала будет создан стек выполнения
, затем будет создан глобальный контекст, и контекст выполнения push
в стек выполнения,
чтобы начать выполнение. , и Global Execution Context start
встречает first
метод, выполняет метод, создает контекст выполнения функции и push
в стек выполнения
для выполнения first
контекста выполнения, выводит first function
встречающую second
метод, выполняет метод. , создает контекст выполнения функции и push
в стек выполнения для
выполнения second
контекста выполнения, выводит second function
first
second
выполнения был выполнен, извлечен из стека и вошел в следующий контекст выполнения,
first
контекст выполнения продолжается
выполнение, вывод Again first function
first
контекст выполнения был выполнен, извлечен из стека и вошел в следующий Контекст выполнения Глобальный контекст выполнения
Глобальный контекст выполнения Продолжить выполнение и вывести Global Execution Context end
Мы используем изображение для подведения итогов
хорошо. После разговора о контексте выполнения и стеке выполнения давайте поговорим о механизме выполнения js. Говоря о механизме выполнения js
js
необходимо понимать синхронные задачи, асинхронные задачи, макрозадачи и микрозадачи в js
.
В js
задачи делятся на синхронные и асинхронные задачи. Итак, что такое синхронные задачи и что такое асинхронные задачи?
Синхронные задачи относятся к задачам, поставленным в очередь для выполнения в основном потоке. Следующая задача может быть выполнена только после выполнения предыдущей задачи.
Асинхронные задачи относятся к задачам, которые не входят в основной поток, но попадают в «очередь задач» (задачи в очереди задач выполняются параллельно с основным потоком только тогда, когда основной поток простаивает и «очередь задач» уведомляет об этом). основной поток, асинхронная задача. Как только задача может быть выполнена, она войдет в основной поток для выполнения. Поскольку это хранилище очередей, оно соответствует правилу «первым пришел — первым обслужен» . К распространенным асинхронным задачам относятся наши setInterval
, setTimeout
, promise.then
и т. д.
ранее были представлены синхронные и асинхронные задачи. Теперь давайте поговорим о цикле событий.
Синхронные и асинхронные задачи входят в разные «места» выполнения соответственно и синхронно входят в основной поток. Только когда предыдущая задача завершена, следующая задача может быть выполнена. Асинхронные задачи не входят в основной поток, но входят в Event Table
и регистрируют функции.
Когда указанное действие будет завершено, Event Table
переместит эту функцию в Event Queue
. Event Queue
— это структура данных очереди, поэтому она соответствует правилу «первым пришел — первым обслужен».
Когда задачи в основном потоке после выполнения пусты, соответствующая функция будет прочитана из Event Queue
и выполнена в основном потоке.
Вышеописанный процесс будет повторяться непрерывно, что часто называют Event Loop .
Подведем итог с помощью картинки
Позвольте мне кратко представить пример
функции test1() { console.log("log1"); setTimeout(() => { console.log("setTimeout 1000"); }, 1000); setTimeout(() => { console.log("setTimeout 100"); }, 100); console.log("log2"); } test1(); // log1, log2, setTimeout 100, setTimeout 1000
Мы знаем, что в js синхронные задачи будут выполняться первыми, а затем асинхронные, поэтому в приведенном выше примере сначала выводятся log1、log2
,
а затем выполняются асинхронные задачи после синхронных. Таким образом, функция обратного вызова с задержкой в 100
миллисекунд сначала выполнит вывод setTimeout 100
Функция обратного вызова с задержкой в 1000
миллисекунд выполнит вывод setTimeout 1000
позже.
Я считаю, что приведенный выше пример относительно прост. пока вы понимаете синхронные и асинхронные задачи, упомянутые автором выше, проблем не будет. Тогда позвольте мне привести вам еще один пример. Друзья, давайте посмотрим, какой будет результат.
функция test2() { console.log("log1"); setTimeout(() => { console.log("setTimeout 1000"); }, 1000); setTimeout(() => { console.log("setTimeout 100"); }, 100); новое обещание((решить, отклонить) => { console.log("новое обещание"); решать(); }).then(() => { console.log("обещание.тогда"); }); console.log("log2"); } test2();
Для решения вышеуказанной проблемы недостаточно знать синхронные и асинхронные задачи. Нам также необходимо знать макрозадачи и микрозадачи.
В js
задачи делятся на два типа: один называется макрозадачой MacroTask
, а другой называется микрозадачой MicroTask
.
Общая макрозадача MacroTask
имеет
основной блок кода
setTimeout()
setInterval
() — Node
requestAnimationFrame() — браузер.
Общая микрозадача MicroTask
имеет
Promise.then()
process.nextTick() — Node
. Пример выше. Он включает в себя макрозадачи и микрозадачи. Каков порядок выполнения макрозадач и микрозадач?
Прежде всего, когда весь script
(как первая макрозадача) начнет выполняться, весь код будет разделен на две части: синхронные задачи и асинхронные задачи. Синхронные задачи будут последовательно входить в основной поток для выполнения. и асинхронные задачи войдут в асинхронную очередь, а затем будут разделены на макрозадачи и микрозадачи.
Макрос-задача входит в Event Table
и регистрирует в ней функцию обратного вызова. Всякий раз, когда указанное событие завершается, Event Table
перемещает эту Event Queue
в очередь событий. Микрозадача также войдет в другую Event Table
и зарегистрируется в ней. Всякий раз, когда указанное событие завершается, Event Table
перемещает эту функцию в Event Queue
Когда задачи в основном потоке завершены и основной поток пуст, будет проверена Event Queue
микрозадачи, если есть задачи. , все Выполнить, если нет, выполнить следующую задачу макроса.
Мы используем изображение, чтобы подвести итог.
Поняв приведенные выше примеры макрозадач и микрозадач в асинхронном режиме, мы легко сможем получить ответ.
Мы знаем, что в js синхронные задачи будут выполняться первыми, а затем асинхронные, поэтому в приведенном выше примере сначала выводятся log1、new promise、log2
. Здесь следует отметить, что основной блок кода нового промиса синхронизируется.
После выполнения макрозадачи все микрозадачи, сгенерированные этой макрозадачей, будут выполнены, promise.then
после выполнения всех микрозадач
, будет выполнена другая макрозадача с задержкой. Функция обратного вызова на 100
миллисекунд установит приоритет выполнения и выведет setTimeout 100
Эта макрозадача не генерирует микрозадачи, поэтому нет микрозадач, которые необходимо выполнить
для продолжения выполнения следующей макрозадачи. Обратный вызов. функция с задержкой 1000
будет определять приоритет выполнения и выводить setTimeout 1000
поэтому после выполнения метода test2 она будет последовательно выводить log1、new promise、log2、promise.then、setTimeout 100、setTimeout 1000
. разные мнения о том, следует ли выполнять
js
сначала с макрозадачами, а затем с микрозадачами или с микрозадачами перед макрозадачами. Автор понимает, что если весь блок кодаjs
рассматривается как макрозадача, то наш порядок выполненияjs
— сначала макрозадача, а затем микрозадача.
Как говорится, лучше один раз потренироваться, чем сто раз посмотреть. Ниже я приведу вам два примера. Если вы умеете делать это правильно, значит, вы овладели знаниями механизма выполнения js
.
Пример 1.
Функция test3() { консоль.журнал(1); setTimeout(функция () { консоль.журнал(2); новое обещание (функция (решение) { консоль.журнал(3); решать(); }).then(function () { консоль.журнал(4); }); консоль.журнал(5); }, 1000); новое обещание (функция (решение) { консоль.журнал(6); решать(); }).then(function () { консоль.журнал(7); setTimeout(функция () { консоль.журнал(8); }); }); setTimeout(функция () { консоль.журнал(9); новое обещание (функция (решение) { консоль.журнал(10); решать(); }).then(function () { консоль.журнал(11); }); }, 100); консоль.журнал(12); } test3();
Давайте разберем его подробно.
Сначала весь блок кода js
выполняется как макрозадача, и последовательно выводятся 1, 1、6、12
.
После выполнения макрозадачи всего блока кода генерируются одна микрозадача и две макрозадачи, поэтому в очереди макрозадач есть две макрозадачи, а в очереди микрозадач — одна микрозадача.
После выполнения макрозадачи все микрозадачи, созданные этой макрозадачей, будут выполнены. Поскольку микрозадача только одна, будет выведено 7
. Эта микрозадача породила еще одну макрозадачу, поэтому в настоящее время в очереди макрозадач есть три макрозадачи.
Среди трех макрозадач первой выполняется та, у которой не установлена задержка, поэтому выводится 8
Эта макрозадача не генерирует микрозадачи, поэтому нет микрозадач для выполнения, и следующая макрозадача продолжает выполняться.
Задержите выполнение макрозадачи на 100
миллисекунд, выведите 9、10
и сгенерируйте микрозадачу, чтобы в очереди микрозадач в данный момент была микрозадача.
После выполнения макрозадачи все микрозадачи, сгенерированные макрозадачой, будут выполнены, поэтому микрозадача будет выполнена
Все микрозадачи в очереди задач выводят 11
Выводы выполнения макрозадачи 2、3、5
с задержкой 1000
миллисекунд, и микрозадача генерируется. Таким образом, в очереди микрозадач в данный момент есть микрозадача
. макрозадача будет выполнена. Все микрозадачи сгенерированы, поэтому все микрозадачи в очереди микрозадач будут выполнены, и будет выведено 4
Таким образом, приведенный выше пример кода выведет 1、6、12、7、8、9、10、11、2、3、5、4
, 12, 7, 8, 9, 10, 11. , 2, 3, 5, 4 по порядку, ребята, вы все сделали правильно?
Пример 2:
Мы немного модифицируем приведенный выше пример 1 и вводим async
и await
асинхронную функцию test4() { консоль.журнал(1); setTimeout(функция () { консоль.журнал(2); новое обещание (функция (решение) { консоль.журнал(3); решать(); }).then(function () { консоль.журнал(4); }); консоль.журнал(5); }, 1000); новое обещание (функция (решение) { консоль.журнал(6); решать(); }).then(function () { консоль.журнал(7); setTimeout(функция () { консоль.журнал(8); }); }); константный результат = ожидание async1 (); console.log(результат); setTimeout(функция () { консоль.журнал(9); новое обещание (функция (решение) { консоль.журнал(10); решать(); }).then(function () { консоль.журнал(11); }); }, 100); консоль.журнал(12); } асинхронная функция async1() { консоль.журнал(13) return Promise.resolve("Promise.resolve"); } test4();
Что выведет приведенный выше пример? Здесь мы легко можем решить проблему async
и await
.
Мы знаем async
и await
на самом деле являются синтаксическим сахаром для Promise
. Здесь нам нужно только знать await
эквивалентен Promise.then
. Таким образом, мы можем понять приведенный выше пример как следующий код
function test4() { консоль.журнал(1); setTimeout(функция () { консоль.журнал(2); новое обещание (функция (решение) { консоль.журнал(3); решать(); }).then(function () { консоль.журнал(4); }); консоль.журнал(5); }, 1000); новое обещание (функция (решение) { консоль.журнал(6); решать(); }).then(function () { консоль.журнал(7); setTimeout(функция () { консоль.журнал(8); }); }); новое обещание (функция (решение) { консоль.журнал(13); returnsolve("Promise.resolve"); }).then((результат) => { console.log(результат); setTimeout(функция () { консоль.журнал(9); новое обещание (функция (решение) { консоль.журнал(10); решать(); }).then(function () { консоль.журнал(11); }); }, 100); консоль.журнал(12); }); } test4();
Легко ли вам получить результат, увидев приведенный выше код?
Во-первых, весь блок кода js
изначально выполняется как макрозадача и последовательно выводит 1、6、13
.
После выполнения макрозадачи всего блока кода генерируются две микрозадачи и одна макрозадача, поэтому в очереди макрозадач есть одна макрозадача, а в очереди микрозадач — две микрозадачи.
После выполнения макрозадачи все микрозадачи, созданные этой макрозадачей, будут выполнены. Итак, будет выведено 7、Promise.resolve、12
. Эта микрозадача породила еще две макрозадачи, поэтому в очереди макрозадач сейчас есть три макрозадачи.
Среди трех макрозадач первой выполняется та, у которой не установлена задержка, поэтому выводится 8
Эта макрозадача не генерирует микрозадачи, поэтому нет микрозадач для выполнения, и следующая макрозадача продолжает выполняться.
Задержите выполнение макрозадачи на 100
миллисекунд, выведите 9、10
и сгенерируйте микрозадачу, чтобы в очереди микрозадач в данный момент была микрозадача.
После выполнения макрозадачи все микрозадачи, сгенерированные макрозадачой, будут выполнены, поэтому микрозадача будет выполнена
Все микрозадачи в очереди задач выводят 11
Выводы выполнения макрозадачи 2、3、5
с задержкой 1000
миллисекунд, и микрозадача генерируется. Таким образом, в очереди микрозадач в данный момент есть микрозадача
. макрозадача будет выполнена. Все сгенерированные микрозадачи будут выполнять все микрозадачи в очереди микрозадач и выводить 4
Таким образом, приведенный выше пример кода выведет 1, 6, 13, 7 1、6、13、7、Promise.resolve、12、8、9、10、11、2、3、5、4
4, ребята, вы все сделали правильно?
Многие друзья могут до сих пор не понимать setTimeout(fn)
Разве не очевидно, что время задержки не установлено, не должно ли оно выполняться немедленно?
Мы можем понимать setTimeout(fn)
как setTimeout(fn,0)
, что на самом деле означает то же самое.
Мы знаем, что js делится на синхронные задачи и асинхронные задачи. setTimeout(fn)
— асинхронная задача, поэтому даже если вы не зададите здесь время задержки, она попадет в асинхронную очередь и не будет выполняться до тех пор, пока не будет запущен основной поток. праздный.
Автор еще раз упомянет об этом, думаете ли вы, что время задержки, которое мы установили после setTimeout
, js
обязательно будет выполнено согласно нашему времени задержки, я так не думаю? Время, которое мы устанавливаем, предназначено только для того, чтобы функция обратного вызова могла быть выполнена, а вот свободен ли основной поток — это другой вопрос. Мы можем привести простой пример.
функция test5() { setTimeout(функция () { console.log("setTimeout"); }, 100); пусть я = 0; в то время как (истина) { я++; } } test5();
Будет ли приведенный выше пример обязательно выводить setTimeout
через 100
миллисекунд? Нет, потому что наш основной поток вошел в бесконечный цикл и у него нет времени для выполнения асинхронных задач очереди?
Здесь упоминается GUI渲染
. Некоторые друзья могут этого не понять. Я расскажу об этом подробно в статье о браузерах. Вот лишь краткое описание.
Поскольку JS引擎线程
и GUI渲染线程
являются взаимоисключающими, чтобы обеспечить упорядоченное выполнение宏任务
и DOM任务
, браузер запускает GUI渲染线程
после результата выполнения одной宏任务
и до того, как выполнение следующей宏任务
, рендеринг страницы.
Таким образом, взаимосвязь между макрозадачами, микрозадачами и рендерингом графического интерфейса следующая:
макрозадача -> микрозадача -> рендеринг графического интерфейса -> макрозадача ->...
[Рекомендация по соответствующему видеоуроку: веб-интерфейс]
Вышеупомянутое углубленный анализ JavaScript. Для получения подробной информации о контексте выполнения и механизме выполнения, пожалуйста, обратите внимание на другие соответствующие статьи на китайском веб-сайте PHP для получения дополнительной информации!