我们都知道js是单线程,它的语言是通过浏览器内核多线程实现异步的,
浏览器的进程其中《渲染进程》有:
- GUI线程:渲染布局,解析css,html,构建dom树,渲染树
- JS引擎线程:解析、执行js代码块,与GUI线程互斥。因为js同样可以修改,html,css,布局等,同时使用有可能产生页面混乱。
- 定时触发线程:处理setTimeout,setInterval
- 事件触发线程:将满足触发条件的事件放入任务队列
- 异步HTTP请求线程:XHR所在的线程
多线程实现异步会放入(入栈)到主线程Event Loop遍历任务队列执行(出栈),不同的浏览器厂商有不同的Event Loop 实现
宏队列和微队列
宏队列,macrotask,也叫tasks.一些异步任务的回调会依次进入 macro task queue,等待后渎被调用,这些异步任务包括:
- setTimeout
- setInterval
- setImmediate(Node独有)
- requestAnimationFrame(浏览器独有)
- I/O
- UI rendering(浏览器独有)
微队列,microtask,也叫jobs。另一些异步任务的回调会依次进入micro task queue,等待后渎被调用,这些异步任务包括:
- process.nextTick(Node独有)
- Promise
- Object.observe
- MutationObserver
(注:这里只针对浏览器和NodeJS)
Javascriot代码的具体流程:
- 执行全局Script代码
- 全局Script代码执行完毕后,调用栈Stack会清空
- 然后从微队列microtask queue中取出位于队首的回调任务,放入调用栈Stack中执行,执行完后microtask queue长度减1
- 如果在执行microtask的过程中,又产生了microtask,那么会加入到队列的末尾,也会在这个周期被调用执行
- 直到microtask queue中所有任务都执行完毕,此时microtask queue为空队列,调用栈Stack也为空
- 然后再取出宏队列macrotask queue中位于队首的任务,放入Stack中执行
- 执行完毕后,调用栈Stack为空
- 重复第3-7个步骤
- ......
这就是浏览器的事件循环Event Loop
这里归纳3个重点:
- 宏队列macrotask一次只从队列中取一个任务执行,执行完后就去执行微任务队列中的任务
- 微任务队列中所有的任务都会被依次取出来执行,知道microtask queue为空
- 只要执行UI rendering,它的节点是在执行所有的microtask之后,下一个macrotask之前,紧跟着执行UI render
巩固测试一下:
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);
// 输出的结果
1
4
7
5
2
6
3
分析流程:
Step1:
console.log(1)
Stack queue:[console]
mircotask queue:[]
marcotask queue:[]
此时打印结果:
1
Step2:
setTimeout(()=>{
console.log(2); // callback1
Promise.resolve().then(()=>{
console.log(3); // callback4
})
})
setTimeout 是宏任务,所以要放入marcotask queue队列
Stack queue:[]
mircotask queue:[]
marcotask queue:[callback1]
此时打印结果:
1
Step3:
new Promise((resolve,reject)=>{
console.log(4);
resolve(5)
}).then((data)=>{
console.log(data); // callback2
})
promise里console.log(4),是同步执行,也就是放入到Stack queue,.then是微任务放入mircotask queue
Stack queue:[console]
mircotask queue:[callback2]
marcotask queue:[callback1]
此时打印结果:
1、4
Step4:
setTimeout(()=>{
console.log(6); // callback3
})
setTimeout 是宏任务,放入到marcotask queue
Stack queue:[]
mircotask queue:[callback2]
marcotask queue:[callback1,callback3]
此时打印结果:
1、4
Step5:
console.log(7); // console
Stack queue:[console]
mircotask queue:[callback2]
marcotask queue:[callback1,callback3]
此时打印结果:
1、4、7
- 好啦,全局Script的代码执行完了,进入下一个步骤,从mircotask queue微任务队列里从队首依次取出执行,直到mircotask queue为空
Step6:
Stack queue:[]
mircotask queue:[callback2]
marcotask queue:[callback1,callback3]
此时打印结果:
1、4、7、5
Step7:
- 这里mircotask queue只有一个任务执行完后,就去marcotask queue宏任务队列,队首取出执行
Stack queue:[]
mircotask queue:[]
marcotask queue:[callback1,callback3]
此时打印结果:
1、4、7、5、2
Step8:
- marcotask 需要注意的是,每一个任务执行完后会先检查Stack queue和mircotask queue里是否有新任务,如果有就执行,没有就依次队首取出执行
- 这里setTimeout 输出结果2后,先检查没有其他队列任务后就继续执行 ,发现promise.then是微任务,就放入到mircotask queue微队列里,同时也执行下一个marcotask队首任务
Stack queue:[]
mircotask queue:[callback4]
marcotask queue:[callback3]
此时打印结果:
1、4、7、5、2、6
Step9:
- 这里宏任务队首执行完后,检查mircotask queue里有一个任务,就开始执行mircotask 队首任务
Stack queue:[]
mircotask queue:[callback4]
marcotask queue:[]
此时打印结果:
1、4、7、5、2、6、3
- 当mircotask任务执行完后,去检查是否有其他任务要执行,这里发现Stack queue:[],mircotask queue:[],marcotask queue:[]都是空的,结束。
摘自:
《理解异步》
https://note.youdao.com/ynoteshare1/index.html?id=0f6e7a2a2ab18a59971569fe5650c113&type=note
《弄懂Event Loop》