JavaScript执行机制(evenLoop)
一、了解JS引擎线程
浏览器是多进程程序,其中渲染进程中包含有JS引擎线程,负责解析与执行JS代码,也称为主线程。浏览器同时只能有一个JS引擎线程在运行JS程序,所以JS是单线程运行的。
JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
所以,为了避免复杂性,从一诞生,JavaScript就是单线程。
那么JS单线程又是如何实现异步的呢?
二、事件循环Event Loop
事件循环(Event Loop)是JS实现异步的一种方法,也是JS的执行机制。
宏任务与微任务
在JavaScript中,任务被分为两种,一种叫宏任务(MacroTask)也叫Task,一种叫微任务(MicroTask)也叫jobs。
- MacroTask(宏任务):包括整体代码script,setTimeout,setInterval
- MicroTask(微任务):Promise,process.nextTick(node.js中)
当JS执行宏任务和微任务时会将它们的回调分别放入宏队列Macrotask Queue和微队列Microtask Queue中。
JS执行栈(调用栈)
JS中所有的任务都会被放到执行栈等待主线程执行。JS执行栈采用的是后进先出的规则,当函数执行的时候,会被添加到栈的顶部,当执行栈执行完成后,就会从栈顶移出,直到栈内被清空。
事件循环执行顺序
(1)全局代码(script)执行,主线程往调用栈里面push任务,顺序执行,当调用栈发现MacroTask(宏任务)和MicroTask(微任务)时,将它们的回调分别放入Macrotask Queue宏任务队列和Microtask Queue微任务队列中,直到执行栈清空。
(2)当执行栈清空后,优先将Microtask Queue微任务队列中的任务取出放入执行栈中执行,直到Microtask Queue微任务队列清空。
(3)当微任务队列执行完毕后调用栈清空,将宏任务队列里的事件放入调用栈中执行。
(4)主线程不断重复上面的第三步。
注意:
1.宏队列Macrotask Queue一次只从队列中取一个任务执行,执行完后就去执行微任务队列中的任务;
2.微任务队列中所有的任务都会被依次取出来执行,直到Microtask Queue为空;
3.如果在执行Microtask Queue的过程中,又产生了Microtask Queue,那么会加入到队列的末尾,也会在这个周期被调用执行。
事件循环如图:
代码分析
好了,话不多说我们来通过代码检验。
----------Talk is cheap,Show me the code.----------
console.log(1);
setTimeout(() => {
console.log(2);
Promise.resolve().then(() => {
console.log(3)
});
});
new Promise((resolve, reject) => {
console.log(4)
resolve(5)
}).then((data) => {
console.log(data);
})
setTimeout(() => {
console.log(6);
})
console.log(7);
运用上面了解到的知识,先自己做一下试试看。
好,我们来分析一下整个流程:
- 整体script作为第一个宏任务进入主线程,遇到console.log,输出 1
- 遇到setTimeout,其回调函数被分发到宏任务队列Macrotask Queue中。我们暂且记为setTimeout1
- 遇到promise,new Promise直接执行,输出4,then被分发到到微任务队列Microtask Queue中。我们暂且记为then1
- 遇到setTimeout,其回调函数被分发到宏任务队列Macrotask Queue中,记为setTimeout2
- 遇到console.log,输出7
宏任务队列 | 微任务队列 |
---|---|
setTimeout1 | then1 |
setTimeout2 |
上表是第一轮事件循环宏任务结束时各Event Queue的情况,此时已经输出了1、4、7
接着执行then1这个微任务,输出5
好了,第一轮事件循环正式结束,这一轮的结果是输出1,4,7,5。那么第二轮时间循环从setTimeout1宏任务开始:
- 执行console.log 输出2
- 执行new Promise 输出3
第二轮事件循环宏任务结束,此时Event Queue
宏任务队列 | 微任务队列 |
---|---|
setTimeout2 | |
第三轮事件循环开始,此时只剩setTimeout2了,执行。
- 直接输出6。
整段代码,共进行了三次事件循环,完整的输出为1,7,4,5,2,3,6。怎么样,你做对了吗?
让我们加大难度:
console.log(1);
setTimeout(() => {
console.log(2);
Promise.resolve().then(() => {
console.log(3)
});
});
new Promise((resolve, reject) => {
console.log(4)
resolve(5)
}).then((data) => {
console.log(data);
Promise.resolve().then(() => {
console.log(6)
}).then(() => {
console.log(7)
setTimeout(() => {
console.log(8)
}, 0);
});
})
setTimeout(() => {
console.log(9);
})
console.log(10);
这道题就不分析啦,试着做下看自己有没有完全掌握。
//正确答案:
1
4
10
5
6
7
2
3
9
8
关于在浏览器里JS的执行机制Event Loop就说到这里,如果文中有错误还请各位帮忙指正,感谢!
本文参考:
阮一峰:JavaScript 运行机制详解:再谈Event Loop.
liuxuan:带你彻底弄懂Event Loop.
ssssyoki:这一次,彻底弄懂 JavaScript 执行机制.