2019-04-19
简单的认识事件循环
先看一段代码:(先热个身)
1 | console.log('script start'); |
打印顺序是什么? 正确答案是:script start, script end, promise1, promise2, setTimeout
没关系 继续看
需要知道的专业名词术语:
synchronous:同步任务
asynchronous:异步任务
task queue/callback queue:任务队列
execution context stack:执行栈
heap:堆
stack:栈
macro-task:宏任务
micro-task:微任务
首先我们还要知道两点:
JavaScript是单线程的语言
Event Loop是javascript的执行机制
异步任务包括:宏任务 和 微任务
同步任务:promise内的函数属于同步 .then()内部才属于异步微服务
new Promise(function(resolve) {
console.log(‘7’); //属于同步任务
resolve();
}).then(function() {
console.log(‘8’) //属于异步微服务
})
宏任务:
包括整体代码script
setTimeout
setInterval
setImmediate
微任务:
原生Promise(有些实现的promise将then方法放到了宏任务中)
process.nextTick (process.nextTick(callback)类似node.js版的"setTimeout")
Object.observe(已废弃)
MutationObserver
记住就行了。
事件循环规则:
骚年 先接我八字真言:先同后异,先微后宏
实例:
Promise.resolve().then(()=>{
console.log('Promise1')
setTimeout(()=>{
console.log('setTimeout2')
Promise.resolve().then(()=>{
console.log('Promise3')
setTimeout(()=>{
Promise.resolve().then(()=>{
console.log('Promise5')
setTimeout(()=>{
console.log('setTimeout6')
},0)
})
console.log('setTimeout4')
},0)
})
},0)
})
先从上到下执行同步任务并全部执行完成
查找异步任务并分配到微任务列表 和 宏任务列表 (每个列表里先进栈先执行)
先执行微服务,并且查找微服务内部的 异步任务然后分配到微任务列表 和 宏任务列表
再执行宏任务,并且查找微服务内部的 异步任务然后分配到微任务列表 和 宏任务列表
既然是事件循环就不是一次就执行完毕了的,因为异步任务会有嵌套,最外层事件最先暴露出来,首先进入第一次循环
再先执行微服务,并查找,分配
再执行宏任务,并查找,分配
依次循环直至没有异步任务
执行结束
1 | Promise.resolve().then(()=>{ |
第一回合 列出所有异步事件并逐个分配到宏服务和微任务里去,然后执行 先微后宏
微:promise1
宏:setTimeout1
先打印Promise1,setTimeout2进宏任务列表
setTimeout1,Promise2进微任务列表
第二回合 执行异步列表 先微后宏
先打印Promise2,
再打印setTimeout2,
到这里是不是有个大概的了解了,测验来了:(先用纸笔根据自己理解先答一遍,然后再看答案)
1 | console.log('1'); |
第一轮事件循环流程分析如下:
- 整体script作为第一个宏任务进入主线程,遇到console.log,输出1。
- 遇到setTimeout,其回调函数被分发到宏任务Event Queue中。我们暂且记为setTimeout1。
- 遇到process.nextTick(),其回调函数被分发到微任务Event Queue中。我们记为process1。
- 遇到Promise,new Promise直接执行,输出7。then被分发到微任务Event Queue中。我们记为then1。
- 又遇到了setTimeout,其回调函数被分发到宏任务Event Queue中,我们记为setTimeout2。
宏任务Event Queue | 微任务Event Queue |
---|---|
setTimeout1 | process1 |
setTimeout2 | then1 |
- 上表是第一轮事件循环宏任务结束时各Event Queue的情况,此时已经输出了1和7。
我们发现了process1和then1两个微任务。
- 执行process1,输出6。
- 执行then1,输出8。
好了,第一轮事件循环正式结束,这一轮的结果是输出1,7,6,8。那么第二轮时间循环从setTimeout1宏任务开始:
- 首先输出2。接下来遇到了process.nextTick(),同样将其分发到微任务Event Queue中,记为process2。
- new Promise立即执行输出4,then也分发到微任务Event Queue中,记为then2
宏任务Event Queue | 微任务Event Queue |
---|---|
setTimeout2 | process3 |
then3 |
- 第三轮事件循环宏任务执行结束,执行两个微任务process3和then3。
- 输出10。
- 输出12。
- 第三轮事件循环结束,第三轮输出9,11,10,12。
- 整段代码,共进行了三次事件循环,完整的输出为1,7,6,8,2,4,3,5,9,11,10,12。(请注意,node环境下的事件监听依赖libuv与前端环境不完全相同,输出顺序可能会有误差)
结尾
听到别人在讨论事件循环,我很想帮他们解答,可是我给出的答案都睡服不了我自己。所以决定自己整理一篇,我觉得可以让人理解的文章。下面还有别人的文章,建议大家多看看。广开言路,纳百家之言,行天下大事。
⬇⬇⬇⬇⬇下方有补充
如有侵权行为,请点击这里联系我删除
补充
2019年4月28号:有一个地方被我忽略了
1 | new Promise(function(resolve) { |
resolve()如果写在定时器里面了,会使then处于等待状态,如果写在定时器外面这输出:6 8 7
2019年5月22号: 练习
1 | new Promise((resolve, reject) => { |
下面是说明用例
new Promise((resolve, reject) => {
console.log("1")
resolve()
}).then(() => {
console.log("2")
setTimeout(function() {
console.log("22")
}, 0)
new Promise((resolve, reject) => {
console.log("3")
resolve()
}).then(() => {
console.log("4")
}).then(() => {
console.log("5")
}).then(() => {
console.log("12")
})
}).then(() => {
console.log("6")
}).then(() => {
console.log("11")
}).then(() => {
console.log("13")
})
总结:
1.promise后面的n多个then 在同一个事件回合里 由上到下 依次加入微列表
- 内外promise的同位then(两个promise的第2个then,由外到内,依次添加到微列表)