看看以下代码会在控制台打印出什么:
function a(){
console.log(1);
setTimeout(()=>{
console.log(2);
})
Promise.resolve().then(function (){
console.log(3);
});
}
setTimeout(function (){
console.log(4);
Promise.resolve().then(a);
}, 0)
Promise.resolve().then((function (){
console.log(5);
}))
console.log(6);
【浏览器进程模型】
浏览器启动后会开启浏览器进程、网络进程、渲染进程等多个进行。
其中,每启动一个标签页的时候,会启动一个渲染进程,渲染进程中会有一个渲染主线程,用于解析html/css,处理图层,执行js,绘制页面,执行计时器和用户操作的回调函数等。
【单线程】
js是单线程的语言,因为它运行在浏览器的渲染主线程中,而渲主线程只有一个。
因为是单线程的,所有有些比较耗时或者不是第一时间需要执行的操作,比如网络请求,计时器,用户点击等,就不能采用同步的方式去执行。
所以需要采用异步的方式来处理这些任务,具体做法就是在主线程中当计时器、网络请求、事件监听等任务发生时,将其交由其他线程去处理,主线程立即结束任务的执行,转而执行后续的代码。
当其他线程执行完毕后,将事先传递的回调函数包装成一个任务,加入到消息队列的末尾,等待主线程去调度执行。
【事件循环/消息循环】
最开始的时候,浏览器的渲染主线程会进入一个无线循环
每一次循环的时候,会去检查消息队列中有没有待执行的任务
如果有的话就取出第一个任务去执行,执行完后进入下一次循环
如果没有的话就进入等待状态,等待队列中有任务的时候被唤醒
其他线程可以向队列中添加任务,新添加的任务会排在队列的末尾,等待主线程来执行,
如果添加的时候主线程是等待状态,则唤醒主线程来拿取任务
在一个浏览器中,会有很多个队列,除了微队列的优先级最高以外,浏览器可以自行决定从其他的几个队列中取第一个来执行
【微队列】
W3C规定每个浏览器必须要有一个微队列,微队列中的任务优先级最高,如果微队列中有未执行的任务,浏览器必须优先从微队列中取出任务来执行。
在chrome浏览器中,至少包含了以下几个常用队列:
- 延时队列,用来存放计时器结束后需要执行的任务,优先级【中】,比如:setTimeout setInterval
- 交互队列,用来存放用户操作后的事件处理任务,优先级【高】,比如:addEventListener
- 微队列,用来存放需要最快执行的任务,优先级【最高】
添加任务到微队列的主要方式:Promise、MutationObserver,例如:Promise.resolve().then(fn)
【简单图解】
根据以上的结论,再来看代码:
function a(){
console.log(1); //第四个输出,因为在第三个输出后又添加了一个微队列任务,就是执行函数a
setTimeout(()=>{
console.log(2); //第六个输出,在第五个输出完毕后,此为第一个待执行的任务,所以再第五个输出后执行
})
Promise.resolve().then(function (){
console.log(3); //第五个输出,因为第4个输出后,向队列中添加了两个任务,一个是延时任务,一个微队列任务,此微队列任务优先执行
});
}
setTimeout(function (){
console.log(4); //第三个输出,因为微队列任务执行完毕后,setTimeout添加的任务在队列第一个
Promise.resolve().then(a);
}, 0)
Promise.resolve().then((function (){
console.log(5); //第二个输出,因为Promise.resolve().then(fn)添加的任务在微队列中,优先级最高
}))
console.log(6); //第一个输出,因为它是渲染主线程中的第一个执行的输出
综上注释,以上代码的输出结果为:
6
5
4
1
3
2