Моделирование реализации движка JS: глубокое понимание механизма JS, а также микрозадач и макрозадач.

JavaScript
Моделирование реализации движка JS: глубокое понимание механизма JS, а также микрозадач и макрозадач.

если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 выполнит код по очереди, сначала выполнив скрипт, который разделен на следующие шаги.

  1. Получить содержимое скрипта или входного файла;

  2. Оберните вышеизложенное в функцию;

  3. как обработчик события «запуск» или «запуск», связанного с программой;

  4. выполнить другие инициализации;

  5. Выдать событие запуска программы;

  6. Событие добавляется в очередь событий;

  7. Механизм 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

Суммировать

Я проверил некоторую информацию, пролистал несколько видео и реорганизовал вышеуказанную проблему.

Ссылаться на