若是JavaScript是单线程的,那么咱们如何像在Java中那样建立和运行线程?web
很简单,咱们使用
events
或设定一段代码在给定时间执行,这种异步性在 JavaScript 中称为event loop
。ajax
在这篇文章中,主要想分析两个点:api
JavaScript 是由 Stack 栈、Heap 堆、Task Queue 任务队列组成的:数组
new
建立的对象;运行如下同步任务时promise
console.log('script start');
console.log('script end');
复制代码
JavaScript 会依次执行代码,首先执行该脚本,具体分为如下几步app
获取该脚本、或输入文件的内容 ;异步
将上述内容包裹在函数内;函数
做为与程序关联的“start”或“launch”事件的事件处理程序;oop
执行其余初始化;ui
发出程序启动事件;
事件被添加到事件队列中;
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 都属于异步任务,它们各自包括以下api:
process.nextTick
,Promises
,MutationObserver
;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
复制代码
查了些资料,翻了一些视频,把这个上述问题从新梳理了一下。