首先先看代码:
async function async1() {
console.log(1);
await async2();
console.log(2);
}
async function async2() {
console.log(3);
}
console.log(4);
setTimeout(function () {
console.log(5);
}, 0);
async1();
new Promise(function (resolve) {
console.log(6);
resolve();
}).then(function () {
console.log(7);
});
console.log(8);
//输出
4 1 3 6 8 2 7 5
原因分析:
1.JS单线程
我们都知道JS是单线程语言,即单个程序只能创建一个执行的线程来完成任务。
为什么JavaScript是单线程?
其实javascript的单线程特点是跟他的用途有关的。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。假如不是单线程的话,在一个线程当我们在给某个DOM节点增加内容的时候,另一个线程正在删除这个DOM节点的内容,那还得了,那不是乱套了吗。所以javascript只能是单线程。
2.同步异步、事件循环
由于JS单线程的特点,js中的任务按顺序一个一个的执行,但当我们遇到需要较长时间才能返回执行结果的任务时,后面的任务就需要等待,带来较高的性能消耗。为了解决这种情况,将任务分为了同步任务和异步任务,并引入了JS事件循环(即EventLoop)机制,来执行异步任务。
同步: 能立即得到结果的任务称为同步任务,多个同步任务在主线程上排队依次执行,前一个任务执行完才能执行下一个任务。即程序执行顺序与任务的排队顺序是一致的、同步的;
异步: 需要一段时间之后才能得到结果的任务称为异步任务,异步任务不进入主线程,而是进入任务队列中,这样遇到异步任务时就不用在主线程等其执行完再执行下一个任务,而是跳过异步任务,继续在主线程执行下一个同步任务。此时程序的执行顺序和任务的排列顺序是不一致的、异步的。
事件循环机制:
整体原则:先同步再异步,先执行宏任务再执行微任务,在执行任何的宏任务之前都需要先保证清空微任务队列
- 当任务进入执行栈,首先判断任务是同步还是异步,同步和异步任务分别进入不同的执行"场所",同步任务进入主线程直接执行,异步的进入Event Table并注册函数。
- 当指定的事情完成时,Event Table会将这个函数移入Event Queue。
- 当主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
- 主线程从“任务队列”中读取执行事件,这个过程是循环不断的,也就是常说的Event Loop(事件循环)机制
3.宏任务、微任务
事件循环机制具体流程:
异步任务可分为宏任务、微任务。当同步任务执行完,先去事件队列查找执行一个宏任务,然后检查microtask队列是否为空,如果不为空则一次性执行所有微任务,微任务执行完继续去执行主线程任务,开启下一次事件循环。
宏任务:<script>
整体代码、setTimeout、setInterval、ajax、DOM事件
浏览器 | Node | |
<script> 整体代码 | √ | √ |
setTimeout | √ | √ |
setInterval | √ | √ |
setImmediate | x | √ |
requestAnimationFrame | √ | x |
Ajax | √ | √ |
DOM事件 | √ | √ |
微任务:promise.then catch finally (await)
# | 浏览器 | Node |
process.nextTick | x | √ |
MutationObserver | √ | x |
Promise.then catch finally | √ | √ |
4.题目解析
//1. 整段代码作为宏任务进入执行
async function async1() {
console.log(1);//5. 输出1
await async2();//6. 运行async2
console.log(2);//8. 放入微任务队列
}
async function async2() {
console.log(3);//7. 输出3
}
console.log(4);//2. 同步代码直接执行,输出4
setTimeout(function () {
console.log(5);//3. 放到宏任务队列
}, 0);
async1();// 4. 运行async1
new Promise(function (resolve) {
console.log(6);//9. 同步代码,输出6
resolve();
}).then(function () {
console.log(7);//10、 放入微任务队列
});
console.log(8);//11、 输出8
简单来说,执行一段代码时,整段代码会作为宏任务进入主线程执行,接下来会有3种情况:
同步代码,直接执行
碰到setTimeout,分发到宏任务队列
碰到Promise.then,分发到微任务队列