еслиJavaScript является однопоточным, так как же нам создавать и запускать потоки, как в Java?
Очень просто, мы используем
eventsИли установите фрагмент кода для выполнения в заданное время, эта асинхронность вызывается в JavaScriptevent loop.
В этой статье я в основном хочу проанализировать два момента:
- Как работает система циклов событий в Javascript;
- Внедрите собственный движок Javascript, чтобы объяснить принцип работы системы циклов событий и продемонстрировать ее очередь задач, цикл выполнения.
Механизм цикла событий в JavaScript
JavaScript сделанStackкуча,Heapкуча,Task QueueОчередь задач состоит из:
- Stack: раньше представлял собой структуру, подобную массиву, используемую для отслеживания текущей выполняемой функции;
-
Heap: используется для назначения
newсозданный объект; - Task Queue: используется для обработки асинхронных задач, после завершения задачи будет указан соответствующий callback для постановки в очередь.
При выполнении следующей задачи синхронизации
console.log('script start');
console.log('script end');
JavaScript выполнит код по очереди, сначала выполнив скрипт, который разделен на следующие шаги.
Получить содержимое скрипта или входного файла;
Оберните вышеизложенное в функцию;
как обработчик события «запуск» или «запуск», связанного с программой;
выполнить другие инициализации;
Выдать событие запуска программы;
Событие добавляется в очередь событий;
Механизм Javascript извлекает событие из очереди и выполняет зарегистрированный обработчик, и он запускается! — «Асинхронное программирование в Javascript CSCI 5828: лекции по основам разработки программного обеспечения 18–20 октября 2016 г.», Кеннет М. Андерсон.
Подводя итог, движок Javascript оборачивает содержимое скрипта вMainвнутри функции и связать ее как программуstartилиlaunchсоответствующий обработчик события, затемMainзапись функцииStack, а потом столкнутьсяconsole.log('script start'), поместите его в стек, выведитеlog('script start'), после завершения работыпопПока весь код не будет завершен до завершения.
Если есть асинхронная задача
console.log('script start');
setTimeout(function callback() {
console.log('setTimeout');
}, 0);
console.log('script end');
Первый шаг, такой же, как и выше, запуститеconsole.log('script start'), а затем столкнуться с **WebAPI** (DOM,ajax,setTimeout)
воплощать в жизньsetTimeout(function callback() {})получение результата - это получениеTimer, продолжить выполнениеconsole.log('end').
В настоящее времяесли timerПосле завершения операции она будет соответствоватьcallbackВойтиTask Queue.
тогда, когдаStackПосле запуска всех функций в середине (то есть ключ к циклу событий: еслиStackЕсли пусто, читать в порядке «первым пришел – первым вышел».Task Queueзадачи внутри), будетcallbackТолкатьStackв исполнении.
Таким образом, результат приведенного выше кода выглядит следующим образом
console.log('script start');
setTimeout(function callback() {
console.log('setTimeout');
}, 0);
console.log('script end');
// log script start
// log script end
// setTimeout
Выше приведен механизм, когда браузеры используют Event Loop для выполнения асинхронных задач.
Микрозадача и макрозадача и реализация движка JS
Microtaskтак же какMacrotaskВсе задачи являются асинхронными, и каждая из них включает следующие API:
- Микрозадача:
process.nextTick,Promises,MutationObserver; - Макрозадача:
setTimeout,setInterval,setImmediateЖдать.
вMacrotaskОчередь — это очередь задач, иMicrotasksОбычно ее выполнение планируется после текущей задачи синхронизации, и ее необходимо обрабатывать в том же цикле, что и все микрозадачи в текущей очереди, как показано ниже.
for (macroTask of macroTaskQueue) {
// 1. 处理 macroTask
handleMacroTask();
// 2. 处理当前 microTaskQueue 所有 microTask
for (microTask of microTaskQueue) {
handleMicroTask(microTask);
}
}
Выполните следующий код
// 1. 首先进入 Stack log "script start"
console.log("script start");
// 2. 执行webAPi,完成后 anonymous function 进入 task queue
setTimeout(function() {
console.log("setTimeout");
}, 0);
new Promise(function(resolve) {
// 3. 立即执行 log "promise1"
console.log("promise1");
resolve();
}).then(function() {
// 4. microTask 安排在当前正在执行的同步任务之后
console.log("promise2");
}).then(function() {
// 5. 同上
console.log("promise3");
});
// 6. log "script end"
console.log("script end");
/*
script start
promise1
script end
promise2
promise3
setTimeout
*/
Таким образом, на выходе получается 1 -> 3 -> 6 -> 4 -> 5 -> 2.
Затем используйте Javascript для имитации JS Engine, эту часть можно просмотреть первой.Microtask and Macrotask: A Hands-on Approach, эту статью, а затем прийти, чтобы выбрать ошибку из следующего кода.
первый вJSEngineВнутренне поддерживает две очереди макро-задач и микро-задачmacroTaskQueue,microTaskQueueи соответствующийjsStackВыполните стек и определите связанные операции.
class JsEngine {
macroTaskQueue = [];
microTaskQueue = [];
jsStack = [];
setMicro(task) {
this.microTaskQueue.push(task);
}
setMacro(task) {
this.macroTaskQueue.push(task);
}
setStack(task) {
this.jsStack.push(task);
}
setTimeout(task, milli) {
this.macroTaskQueue.push(task);
}
}
Затем определите соответствующий операционный механизм и операцию инициализации.
class JsEngine {
...
// 与event-loop中的初始化对应
constructor(tasks) {
this.jsStack = tasks;
this.runScript(this.runScriptHandler);
}
runScript(task) {
this.macroTaskQueue.push(task);
}
runScriptHandler = () => {
let curTask = this.jsStack.shift();
while (curTask) {
this.runTask(curTask);
curTask = this.jsStack.shift();
}
}
runMacroTask() {
const { microTaskQueue, macroTaskQueue } = this;
// 根据上述规律,定义macroTaskQueue与microTaskQueue执行的先后顺序
macroTaskQueue.forEach(macrotask => {
macrotask();
if (microTaskQueue.length) {
let curMicroTask = microTaskQueue.pop();
while (curMicroTask) {
this.runTask(microTaskQueue);
curMicroTask = microTaskQueue.pop();
}
}
});
}
// 运行task
runTask(task) {
new Function(task)();
}
}
Используйте приведенный выше Js Engine для запуска следующего кода
const scriptTasks = [
`console.log('start')`,
`console.log("Hi, I'm running in a custom JS engine")`,
`console.log('end')`
];
const customJsEngine = new JsEngine(scriptTasks);
customJsEngine.setTimeout(() => console.log("setTimeout"));
customJsEngine.setMicro(`console.log('Micro1')`);
customJsEngine.setMicro(`console.log('Micro2')`);
customJsEngine.runMacroTask();
конечный результат
start
Hi, I'm running in a custom JS engine
end
Micro1
setTimeout
Суммировать
Я проверил некоторую информацию, пролистал несколько видео и реорганизовал вышеуказанную проблему.