Сразу понять механизм выполнения JavaScript

внешний интерфейс JavaScript
Сразу понять механизм выполнения JavaScript

личный блог

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

Как выполняется JavaScript?

Механизм выполнения — цикл событий

обычно говорят JavaScript Engine (движок JS) отвечает за выполнение одного за другимchunk(можно понимать как事件块) процедуры, каждаяchunkкак правилоfunctionэто единица, аchunkПосле завершения выполнения будет выполнено следующееchunk. СледующийchunkЧто тогда? зависит от текущегоEvent Loop Queue(очередь цикла событий) лидер очереди.

обычно слышалJavaScript Engine а такжеJavaScript runtime что это такое?

  • Javascript Engine: Js-движок, отвечающий за интерпретацию и компиляцию кода, превращая его в код, который можно передать машине для запуска (выполняемые команды).
  • Среда выполнения Javascript: среда выполнения Js, в основном предоставляющая некоторые внешние интерфейсы вызова. Например, среда браузера:window,DOM. И среда Node.js:require,export

Event Loop Queue (очередь цикла событий) хранит все сообщения, каждое сообщение связано с функцией,JavaScript Engine (далее JS-движок) выполнит их в порядке следования сообщений в очереди, то есть выполнитchunk.

Например

setTimeout( function() {
    console.log('timeout')
}, 1000)

Когда JS-движок выполняется, его можно разделить на 3 этапа.

  1. Зависит отsetTimeoutЗапустите таймер (1000 мс) для выполнения
  2. После завершения выполнения появляется возможностьcallbackположить вEvent Loop Queue
  3. этот обратный вызов выполняется

каждый шаг - этоchunk, можно обнаружить, что на шаге 2 очень важно получить возможность, поэтому даже если задержка составляет 1000 мс, она может быть неточной. Потому что, если впереди есть другие задачи, он может хотя бы дождаться завершения программ, соответствующих другим сообщениям.callbackЖми в очередь, дадим позже 🌰


сделать это один за другим, как этоchunkпроцесс называетсяEvent Loop(事件循环).

По словам Учителя Руана:

Общий вид: стек и куча генерируются при выполнении основного потока, код в стеке отвечает за вызов различных API и добавление событий (щелчок, загрузка, выполнение) в очередь задач, пока выполняется код в стеке. , После этого он прочитает очередь задач и по очереди выполнит функции обратного вызова, соответствующие этим событиям.

Поток исполняемого механизма

Синхронизация напрямую входит в основной поток для выполнения.Если она асинхронная, задача не входит в основной поток, а входит в «очередь задач».Только «очередь задач» информирует основной поток о том, что асинхронная задача может быть выполнена. основной поток для выполнения. Основной поток считывает события из «очереди задач», и этот процесс цикличен, поэтому весь механизм работы также называется Event Loop.

Мы все знаем, что движок JS правJavaScriptВыполнение программы является однопоточным, чтобы предотвратить конфликт или невозможность одновременной работы с данными, ноJavaScript Runtime(вся среда выполнения) не является однопоточным, и почти все асинхронные задачи выполняются одновременно, например, несколькоJob Queue,Ajax,Timer,I/O(Node)и т.п.

И Node.js будет немного другим, вnode.jsПри запуске файл типаwhile(true)Тело цикла , каждый раз, когда тело цикла выполняется один раз, вызывается один разtick, каждыйtickПроцесс заключается в том, чтобы увидеть, есть ли событие, ожидающее обработки, если да, выньте функцию обратного вызова, связанную с событием, и выполните ее, а затем выполните в следующий раз.tick. узлаEvent Loopи браузеры разные.Event LoopКаждый опрос: сначала выполняется основной код, а асинхронный код будет передан в соответствующую очередь в течение периода.Затем сначала выполните все nextTick(), а затем все остальные микрозадачи.

очередь задач

очередь задачtask queueимеют微任务队列а также宏任务队列

  • Существует только одна очередь микрозадач
  • Задач макроса может быть несколько

А пока нарисуем грубый набросок


Конкретную часть обсудим позже, а потом поговорим о синхронизации и асинхронности.

Механизм выполнения - синхронные и асинхронные задачи

События делятся на синхронные и асинхронные

Синхронизировать задачу

Задачи синхронизации переходят непосредственно в основной поток для выполнения.
console.log('1');

var sub = 0;
for(var i = 0;i < 1000000000; i++) {
    sub++
}
console.log(sub);

console.log('2');
.....

Тот, кто умеет программировать, знает, что при распечаткеsubперед значением система не будет распечатывать2из. Выполнять в порядке поступленияchunk。

еслиExecution Context Stack(执行上下文堆栈)

function log(str) {
    console.log(str);
}
log('a');

От порядка исполнения, сначалаlog('a')в стек, затемconsole.log('a')повторно нажать, выполнитьconsole.log('a')поп,log('a')Затем вытащите стек.

асинхронная задача

Асинхронные задачи должны указывать функцию обратного вызова, так называемую «обратный вызов» (обратный вызов), то есть эти коды будут зависеть в главной ните. Асинхронные задачи вEvent TableПосле этого, когда обозначенная вещь будет завершена, присоединяйтесь к асинхронной задаче.Event Queue, дождавшись завершения задачи в основном потоке, выполнить асинхронную задачу в Event Queue, то есть выполнить соответствующую функцию обратного вызова.

Указанная вещь может быть временем setTimeout 🌰

var value = 1;
setTimeout(function(){
    value = 2;
}, 0)
console.log(value);  // 1

Из этого примера легко понять, что даже если время схватывания короткое,setTimeoutВсе еще нужно дождаться завершения основного потока перед выполнением, что приведет к исходной ссылкеvalueстоимость

🌰

console.log('task1');

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

var sub = 0;
for(var i = 0;i < 1000000000;i++) {
    sub++
}
console.log(sub);
console.log('task3');


Проанализируйте это

  • task1 входит в основной поток и немедленно выполняется
  • задача2 входитEvent Table, зарегистрировать событиеsetTimeoutпосле входаEvent Queue, подождите, пока основной поток завершит выполнение
  • После того, как подпрограмма назначена, она входит в цикл for и увеличивается, а основной поток всегда занят.
  • После расчета распечатайте суб, основной нить продолжается кусок
  • task3 входит в основной поток и немедленно выполняется
  • Очередь основного потока была очищена, выполните задачу в очереди событий и напечатайте задачу2.

Независимо от того, как долго вычисляется цикл for, пока основной поток всегда занят, он не будет выполненEvent Queueзадачи в очереди. Если основной квест не выполнен. все, что мы обычно говоримsetTimeoutизtimeЭто не стандартно, если быть точным, так и должно быть.大于等于这个time

Приходите 🌰 ощутить результат

var sub = 0;
(function setTime(){
	let start = (new Date()).valueOf();//开始时间
	console.log('执行开始',start)
	setTimeout(()=>{ 
	   console.log('定时器结束',sub,(new Date()).valueOf()-start);//计算差异
	},0);
})();
for(var i = 0;i < 1000000000;i++) {
    sub++
}
console.log('执行结束')

На самом деле задержка будет намного больше, чем ожидалось, достигнув3004毫秒


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

Как рассчитать AJAX

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

console.log('start');

$.ajax({
    url:'xxx.com?user=123',
    success:function(res){
        console.log('success')
    }
})
setTimeout(() => {
    console.log('timeout')
},100);

console.log('end');

Ответ нет, может быть

start
end
timeout
success

это может быть также

start
end
success
timeout

Первые два шага сомнений не вызывают, они выполняются как синхронные функции, причина проблемы кроется в ajax.

Ранее мы говорили, что асинхронные задачи должны иметьcallback, аяксcallbackдаsuccess(), то есть только при успешном выполнении запроса срабатывает соответствующий обратный вызовsuccess()Он будет помещен в очередь задач (Event Queue) для ожидания выполнения основного потока. В период, когда результат запроса возвращается, последнийsetTimeoutОчень вероятно, что указанные условия были соблюдены (执行100毫秒延时完毕) помещает свою функцию обратного вызова в очередь задач и другие основные потоки для выполнения. В настоящее время результат ajax не может быть возвращен...

Механизм выполнения обещаний

добавить больше

console.log('执行开始');

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

new Promise(function(resolve) {
    console.log('进入')
    resolve();
}).then(res => console.log('Promise执行完毕') )

console.log('执行结束');

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


Здесь следует отметить, что, строго говоря,Промисы принадлежат очереди заданий,Толькоthenявляется асинхронным.

Что такое очередь заданий

Очередь заданий — это новая концепция в ES6.

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

  • Среда выполнения JavaScript (среда выполнения JS) может иметь несколько очередей заданий, но только одну очередь цикла событий.
  • После того, как механизм JS выполнит текущий фрагмент, он сначала выполнит все очереди заданий, а затем выполнит очередь цикла событий.
Обещать одинthenэтоJob Queue.

Процесс анализа:

  1. При столкновении с задачей синхронизации войдите в основной поток, чтобы выполнить ее напрямую, и распечатайте"执行开始"
  2. встретитьsetTimeoutАсинхронная задача помещается в таблицу событий для выполнения, после выполнения условий ставится в очередь макрозадач очереди событий и ожидает выполнения основного потока.
  3. воплощать в жизньPromiseпоставить вJob QueueСначала выполнить, выполнить задачи синхронизации и распечатать"进入"
  4. вернутьresolve()Запустите функцию обратного вызова then и поместите ее в очередь микрозадач Event Queue.Ожидание выполнения основного потока
  5. Выполнить распечатку задачи синхронизации"执行结束"
  6. Основной поток очищается, чтобыEvent Queueиз微任务队列Снимите задачу и приступайте к выполнению. распечатка"Promise执行完毕"
  7. Очередь микрозадач очищается, а выполнение задачи извлекается из очереди макрозадач и распечатывается."timeout"

🌰 плюс 

console.log("start");

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

new Promise((resolve) => {
  resolve();
})
.then(() => {
  return console.log("A1");
})
.then(() => {
  return console.log("A2");
});

new Promise((resolve) => {
  resolve();
})
.then(() => {
  return console.log("B1");
})
.then(() => {
  return console.log("B2");
})
.then(() => {
  return console.log("B3");
});

console.log("end");

распечатать результат


Используйте то, что я только что сказал, и проанализируйте это снова

  • асинхронная задача setTimeout, поместите обратный вызов в очередь задач макроса Event Queue после выполнения таблицы событий и дождитесь выполнения основного потока
  • Обещание введено в очередь работы, и он входит в основной нить для выполнения, и возвращаетresolve(),вызыватьA1 thenФункция обратного вызова помещается в очередь микрозадач и ожидает выполнения основного потока.
  • Ко второму промису, как и выше, поместите его в очередь заданий для выполнения и поместитеB1 thenФункция обратного вызова ставится в очередь микрозадач
  • Выполните функцию синхронизации, напрямую войдите в основной поток для выполнения и распечатайте"end"
  • Нет синхронной задачи, начните вынимать асинхронную задачу из Очереди задач, то есть Очереди событий и запускайте выполнение
  • Убейте лидера команды первымA1 then()Функция обратного вызова начинает выполняться и выводит"A1",вернутьpromiseвызыватьA2 then()Функция обратного вызова добавлена ​​в начало очереди микрозадач. Лидер командыB1 then()
  • Уберите это из очереди микрозадачB1 thenФункция обратного вызова, запуск выполнения, возврат обещания для срабатыванияB2 then()Добавлена ​​функция обратного звонка в лидер группы микрозадач.A2 then(), затем выньтеA2 then()Выполнить, на этот раз без обратного вызова
  • Перейдите к группе микрозадач, чтобы получить выполнение обратного вызова, повторите опрос и распечатайтеB2а такжеB3.
  • После выполнения микрозадачи подойдите к руководителю группы макрозадач, чтобы снять ее.setTimeoutФункция обратного вызова помещается в основной поток для выполнения и выводит"setTimeout".

В этом случае Promise должен понимать, а микрозадачи и макрозадачи? Многие люди могут быть немного незнакомы с этим, но после прочтения этого вы должны понять разницу между двумя

Асинхронные задачи делятся на макрозадачи и микрозадачи.

Макрозадачи: setTimeout, setInterval, setImmediate(node.js), ввод-вывод, отрисовка пользовательского интерфейса
Микрозадачи: process.nextTick(node.js), Promises, Object.observe, MutationObserver

Давайте сначала посмотрим на конкретный API:

process.nextTick

узловой метод,process.nextTickТекущую задачу можно добавить в хвост стека выполнения, то есть выполнить до следующего Event Loop (основной поток считывает «очередь задач»). То есть указанная задача должна выполняться перед всеми асинхронными задачами. а такжеsetTimeout(fn,0)Так же, как.

process.nextTick(callback)

setImmediate

Node.js0.8 не имел setImmediate раньше, добавляя события в конец текущей «очереди задач», сказал чиновник.setImmediateуказанная функция обратного вызова, аналогичнаяsetTimeout(callback,0), поместит событие в следующий цикл событий, поэтому оно также будет быстрее, чемnextTickМедленное исполнение, одно - нужно знатьsetImmediateа такжеnextTickразница.nextTickНесмотря на то, что он выполняется асинхронно, он не дает шанса другим событиям ввода-вывода выполниться, иsetImmediateвыполняется на следующемevent loop. во всяком случаеprocess.nextTick()приоритет выше, чемsetImmediate

setImmediate(callback)

MutationObserver

должно произойти вsetTimeoutРаньше вы могли думать об этом какsetImmediate.MutationObserverэто конструктор, который принимаетcallbackПараметры, функция обратного вызова, используемая для обработки изменений узлов, возвращает два параметра.

  • мутации: список записей об изменении узла (sequence)
  • наблюдатель: создайте объект MutationObserver.
var observe = new MutationObserver(function(mutations,observer){
        // code...
})

Не говорите слишком много здесь, вы можете узнатьконкретное использование

Object.observe

Метод Object.observe используется для указания функции обратного вызова, которая будет вызываться, когда объект отслеживается на предмет изменения свойства.

Object.observe(obj, function(changes){
   changes.forEach(function(change) {
        console.log(change,change.oldValue);
    });
});
При каких обстоятельствах он сработает?
  • Изменения в исходном объекте JavaScript
  • Изменения при добавлении, изменении или удалении свойств
  • Изменения, когда элементы в массиве добавляются или удаляются
  • Изменения в прототипе объекта

давай побольше 🌰

Суммировать:

приоритет задачи

同步任务 >>>  process.nextTick >>> 微任务(ajax/callback) >>> setTimeout = 宏任务 ??? setImmediate

setImmediateЭто дождаться очередного события polling, которое должно выполниться по истечении этого времени, значит надо рисовать???

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

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

console.log('1');

setTimeout(function() {
    console.log('2');

    process.nextTick(function() {
        console.log('3');
    })

    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
}, 1000); //添加了1000ms
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})

setImmediate(function(){//添加setImmediate函数
    console.log('13')
})

Первый цикл событий

  • идти1Когда задача синхронизации напечатана напрямую
  • встретитьsetTimeout, введите выполнение задачи1000msЗадержка, не достигнутая на данный момент, игнорируем ее и продолжаем снижаться.
  • встретитьprocess.nextTick, помещенный в конец стека выполнения (выполнится до выполнения асинхронной задачи).
  • встретитьPromiseПоместите его в очередь заданий, движок JS в настоящее время не имеет чанка, он напрямую входит в основной поток для выполнения и распечатывает7
  • вызыватьresolve(),Будуthen 8Поместите его в очередь микрозадач и подождите, пока основной поток выполнится, и продолжайте опускаться.
  • встретитьsetTimeout, выполнение завершено,setTimeout 9обратный вызов, который помещается в очередь задач макроса
  • встретитьsetImmediate, поместите его обратный вызов в таблицу событий и дождитесь следующего цикла выполнения цикла событий.

Закончил с первого раза 1,7

текущая очередь


Номер два Готов к работе!

  • Синхронной задачи нет, готов выполнять асинхронные задачи, JS движок смотрит на нее: "Эй! Молодец, есть процесс", а потом вынимаетprocess.nextTickФункция обратного вызова выполняется, выводит6
  • Продолжать идти в главу команды Micro Task, чтобы вынутьthen 8,распечатка8.
  • Когда очередь микрозадач опустеет, перейдите в очередь макрозадач, чтобы вынуть ее.setTimeout 9 callbackвыполнить, распечатать9
  • Продолжайте выполнять и сталкивайтесьprocess.nextTick 10, поставить в очередь событий и дождаться выполнения
  • встретитьPromise, поместите обратный вызов в очередь заданий, в настоящее время фрагмента нет, выполните распечатку 11
  • вызыватьresolve(), добавьте функцию обратного вызоваthen 12, поставить в очередь микрозадач

Этот цикл событий еще не завершен, задача синхронизации выполнена, и текущая очередь задач


  • вынуть сноваprocess.nextTick 10,распечатка10
  • Заходим в очередь микрозадач, вынимаемthen 12 выполнить, распечатать12
  • Этот опрос цикла событий заканчивается, выньтеsetImmediateраспечатка13.

После завершения второго тура опроса распечатайте6,8,9,11,10,12,13

На данный момент задачи нет, речь пойдет о1000ms,предыдущийsetTimeout После того, как задержка будет завершена, поместите ее в задачу макроса

  • setTimeoutВойдите в основной поток, чтобы начать выполнение.
  • Встречайте задачу синхронизации, выполняйте ее напрямую и распечатывайте2
  • встретитьprocess.nextTick, обратный вызов помещается в очередь событий, ожидая завершения задачи синхронизации.
  • встретитьPromise, обратный вызов помещается в очередь заданий, чанка в данный момент нет, он входит в основной поток для выполнения и выводит4
  • вызыватьresolve(), Будуthen 5В очередь микрозадач

Синхронизация завершена, посмотрите текущую очередь


Остальное легко

  • выигратьprocess.nextTick 3 callbackвыполнить, распечатать3
  • Избавьтесь от микрозадач then 5,распечатка5
  • over

Общий порядок печати

1
7
6
8
9
11
10
12
13
2
4
3
5

эммм... может потребоваться еще несколько чтений, чтобы переварить.

Web Worker

теперь естьWeb Worker, это независимый поток, но он не изменил исходный отдельный поток,Web Workerпросто дополнительный поток со своим собственным пространством памяти (стеком, кучей) иEvent Loop Queue. Чтобы общаться с такой другой нитью, толькоpostMessage. однаждыpostMessageв другой темеEvent Loop Queue Добавьте сообщение в . Говоря оpostMessageНекоторые люди могут подуматьService Work, но это два разных

Разница между веб-воркером и сервис-воркером

Service Worker:
Фоновая служба, которая обрабатывает сетевые запросы. Идеальное решение для фоновой синхронизации или push-уведомлений в офлайн-ситуациях. Невозможно напрямую взаимодействовать с DOM. Коммуникация (между страницей и Service Worker) должна проходитьpostMessageметод, есть еще одна статья о локальном хранилище, которое используется для доступа к сервисной работе Google PWA в автономном режиме на странице,Если интересно, можете посмотреть

Web Worker:
Имитация многопоточности, позволяя выполнять сложные сценарии в фоновом режиме, чтобы они не блокировали выполнение других сценариев. Это идеальное решение, позволяющее поддерживать отзывчивость пользовательского интерфейса, а также выполнять функции, интенсивно использующие процессор. Невозможно напрямую взаимодействовать с DOM. Коммуникация должна пройтиpostMessageметод

Если вы все еще не удовлетворены, вы можете попытаться углубиться в другую статью Promise——Позвольте вам понять async/await за один раз и решить ад обратного вызова