模拟实现 JS 引擎:深入了解 JS机制 以及 Microtask and Macrotask

栏目: JavaScript · 发布时间: 7年前

内容简介:如果很简单,我们使用在这篇文章中,主要想分析两个点:

如果 JavaScript是单线程的 ,那么我们如何像在 Java 中那样创建和运行线程?

很简单,我们使用 events 或设定一段代码在给定时间执行,这种异步性在 JavaScript 中称为 event loop

在这篇文章中,主要想分析两个点:

  • Javascript 中的 event loop 系统是如何工作;
  • 实现自定义 Javascript 引擎来解释 event loop 系统的工作原理并演示其任务队列、执行周期。

JavaScript 中的 Event Loop 机制

JavaScript 是由 Stack 栈、 Heap 堆、 Task Queue 任务队列组成的:

  • **Stack **:用来是一种类似于数组的结构,用于跟踪当前正在执行的函数;
  • Heap :用来分配 new 创建的对象;
  • Task Queue :是用来处理异步任务的,当该任务完成时,会指定对应回调进入队列。

运行以下同步任务时

console.log('script start');
console.log('script end');
复制代码

JavaScript 会依次执行代码,首先执行该脚本,具体分为以下几步

  1. 获取该脚本、或输入文件的内容 ;

  2. 将上述内容包裹在函数内;

  3. 作为与程序关联的“start”或“launch”事件的事件处理程序;

  4. 执行其他初始化;

  5. 发出程序启动事件;

  6. 事件被添加到事件队列中;

  7. Javascript引擎将该事件从队列中拉出并执行注册的处理程序,然后运行!— “Asynchronous Programming in Javascript CSCI 5828: Foundations of Software Engineering Lectures 18–10/20/2016” by Kenneth M. Anderson

总结一下就是,Javascript 引擎会将脚本内容包裹在 Main 函数内,并将其关联为程序 startlaunch 事件的对应处理程序,然后 Main 函数进入 Stack ,然后遇到 console.log('script start') ,将其入栈,输出 log('script start') ,待其运行完毕之后 出栈 ,直到所有代码运行完。

模拟实现 JS 引擎:深入了解 JS机制 以及 Microtask and Macrotask

如果存在异步任务时

console.log('script start');
setTimeout(function callback() {
    console.log('setTimeout');
}, 0);
console.log('script end');
复制代码

第一步,同上图,运行 console.log('script start') ,然后遇到**WebAPIs **( DOMajaxsetTimeout

模拟实现 JS 引擎:深入了解 JS机制 以及 Microtask and Macrotask

执行 setTimeout(function callback() {}) 得到结果是在得到一个 Timer ,继续执行 console.log('end')

此时 如果 timer 运行完成,会让其对应 callback 进入 Task Queue

模拟实现 JS 引擎:深入了解 JS机制 以及 Microtask and Macrotask

然后当 Stack 中函数全部运行完成之后(也就是 Event Loop 的关键:如果 Stack 为空的话,按照先入先出的顺序读取 Task Queue 里面的任务),将 callback 推入 Stack 中执行。

模拟实现 JS 引擎:深入了解 JS机制 以及 Microtask and Macrotask

所以上述代码的结果如下

console.log('script start');
setTimeout(function callback() {
	console.log('setTimeout');
}, 0);
console.log('script end');
// log script start
// log script end
// setTimeout
复制代码

以上是游览器利用 Event Loop 执行异步任务时的机制。

Microtask 和 Macrotask 以及实现 JS 引擎

Microtask以及 Macrotask 都属于异步任务,它们各自包括如下api:

  • Microtask: process.nextTickPromisesMutationObserver
  • Macrotask: setTimeoutsetIntervalsetImmediate 等。

其中 Macrotask 队列就是任务队列,而 Microtasks 则通常安排在当前正在执行的同步任务之后执行,并且需要与当前队列中所有 Microtask 都在同一周期内处理,具体如下

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 内部维护宏任务、微任务两个队列 macroTaskQueuemicroTaskQueue 以及对应的 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
复制代码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Machine Learning

Machine Learning

Kevin Murphy / The MIT Press / 2012-9-18 / USD 90.00

Today's Web-enabled deluge of electronic data calls for automated methods of data analysis. Machine learning provides these, developing methods that can automatically detect patterns in data and then ......一起来看看 《Machine Learning》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具