模拟实现 JS 引擎:深入了解 JS机制 以及 Microtask and Macrotask
栏目: JavaScript · 发布时间: 5年前
内容简介:如果很简单,我们使用在这篇文章中,主要想分析两个点:
如果 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 会依次执行代码,首先执行该脚本,具体分为以下几步
-
获取该脚本、或输入文件的内容 ;
-
将上述内容包裹在函数内;
-
作为与程序关联的“start”或“launch”事件的事件处理程序;
-
执行其他初始化;
-
发出程序启动事件;
-
事件被添加到事件队列中;
-
Javascript引擎将该事件从队列中拉出并执行注册的处理程序,然后运行!— “Asynchronous Programming in Javascript CSCI 5828: Foundations of Software Engineering Lectures 18–10/20/2016” by Kenneth M. Anderson
总结一下就是,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')
,然后遇到**WebAPIs **( DOM
, ajax
, setTimeout
)
执行 setTimeout(function callback() {})
得到结果是在得到一个 Timer
,继续执行 console.log('end')
。
此时 如果 timer
运行完成,会让其对应 callback
进入 Task Queue 。
然后当 Stack 中函数全部运行完成之后(也就是 Event Loop 的关键:如果 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 执行异步任务时的机制。
Microtask 和 Macrotask 以及实现 JS 引擎
Microtask以及 Macrotask 都属于异步任务,它们各自包括如下api:
- Microtask:
process.nextTick
,Promises
,MutationObserver
; - Macrotask:
setTimeout
,setInterval
,setImmediate
等。
其中 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
内部维护宏任务、微任务两个队列 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 复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。