前面我们说了浏览器的事件循环,也就是浏览器执行代码的顺序,知道了浏览器是先运行同步代码,再运行异步代码。但其实,浏览器的事件循环里,异步代码也是分为了两类,一类是微任务,一类是宏任务。
微任务
微任务的优先级高于宏任务,也就是说,当浏览器执行完同步代码后,浏览器会先去微任务队列中查找微任务,如果有微任务的话就将这个任务出列,进入执行栈中,执行代码。等到微任务队列中没有任务之后,才会去宏任务队列中进行查找。
js里的微任务一般就是Promise和Object.observe、MutationObserver,我们重点会Promise就可以了。
宏任务
宏任务是优先级最低的,必须等同步代码和微任务队列中都执行完毕后,才会执行宏任务队列中的代码。宏任务一般包括AJAX、setTimeout、setInterval、DOM事件等。
例子:(一道面试题)
async function async1() {
console.log('async1 start');
await async();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(()=>{
console.log('setTimeout')
},0)
async1();
new Promise(function(resolve){
console.log('promise1');
resolve();
}).then(function(){
console.log('promise2')
});
console.log('script end');
它的输出结果是什么呢?其实看它的输出结果也就是看这段代码的运行先后顺序。
步骤:
- 前面是声明函数,没有输出。
- 执行
console.log('script start')输出'script start'; - 发现是定时器,将它放入宏任务中;
- 执行
async1函数- 执行
console.log('async1 start'), 输出'async1 start' - 执行await后面,也就是async2();
- 执行
async2() - 执行
console.log('async2'), 输出'async2';
- 执行
- await就是.then操作,属于异步,且是微任务,放入微任务队列中;
- 退出async1函数
- 执行
- 执行
new Promise- 执行
console.log('promise1'), 输出'promise1'; - resolve();
- .then属于异步,放入微任务队列中;
- 退出promise
- 执行
- 执行
console.log('script end'),输出'script end' - 同步代码执行完毕,下面去扫描微任务队列
- 发现了async1函数中的
await; - 将
await出列,放入执行上下文栈中执行 - 执行await,这里也就是等于
p.then()(p就是async2()的返回值) - 执行
console.log('async1 end'),输出'async1 end'
- 发现了async1函数中的
- 继续扫描微任务队列
- 发现
.then(function(){console.log('promise2')}); - 将它出列,放入执行上下文栈中执行;
- 执行'console.log('promise2')',输出'promise2'
- 发现
- 继续扫描微任务队列,发现没有,去扫描宏任务队列
- 发现
setTimeout(); - 将它出列,放入执行上下文栈中执行;
- 执行
console.log('setTimeout'),输出'setTimeout'
- 发现
- 执行完毕
其实这个还是挺简单的吧,搞懂原理,按照顺序就行了!
注:像异步的setTimeout和ajax请求之类的,它不是将源代码直接放入异步队列中,而是先放入它对应的那个执行器中,比如setTimeout就是放入执行定时器那个队列中中,然后开始计时,再将回调任务放入异步队列中,等到同步代码执行完后,再将回调放入执行栈中。这样形成一个类似多线程的操作。
如果有总结错误的地方,请大家多多指教,我也还在开始摸索js的底层,哈哈!!