请写出以下代码的输出结果,这是一道笔试中非常常见的题目,就问你熟不熟悉,眼不眼熟,但是很多同学往往摔在这道题里,那我们就很有必要展开讲讲了,只有研究它,才能战胜它,所谓知己知彼百战百胜嘛。只要搞清楚其中的规则,万变不离其宗!题目再怎么变,你都能写出答案来。
首先这类题目常见的会包含同步任务、异步任务,异步任务又有宏任务和微任务,宏任务又有setTimeout、setInterval、script(整体代码)、UI交互等,而微任务包括Promise.then、process.nextTick等,将这些不同类型的任务放到一起,就容易混淆我们的判断。
首先我们要知道JavaScript是一门单线程语言,也就是说它不会同时处理多个任务,它只能一个一个地处理,就好比你去食堂打菜,食堂只有一个打菜的窗口和一个打菜的阿姨,那你就得排队,排到你了阿姨就给你打,你总不能自己抢上去打吧,那如果这样的话,一个个地等,如果一个任务耗时很长的话,那岂不是后面的都要等着,那像我们的网站,总不能因为一张图片太大加载不出来,就整个网站都在那空着吧,所以有了同步任务和异步任务来解决类似的问题,当我们打开网站时,网页的渲染过程就是一大堆同步任务,像页面的结构,而像加载图片音乐之类占用资源大耗时久的任务,就是异步任务。我们来看一个任务执行的顺序图:
上图要表达的内容用文字来表述的话:
- 同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。
- 当指定的事情完成时,Event Table会将这个函数移入Event Queue。
- 主线程内的任务执行完毕为空(js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数),会去Event Queue读取对应的函数,进入主线程执行。
- 上述过程会不断重复,也就是常说的Event Loop(事件循环)。
来看一个简单的例子:
const promise = new Promise((resolve,reject)=>{
console.log(1)
console.log(2)
resolve()
})
promise.then(()=>{
console.log(3)
})
console.log(4)
//代码从上到下看,只有promise.then是异步任务,所以先把同步任务执行完之后,就会执行异步任务
//打印出来就是 1 2 4 3
但是面试题往往不会这么简单,往往都是像文中最开始放的那道题那样,多个任务,同步异步,宏任务微任务放在一起,那怎么办呢,不要慌,我们先来理解一下事件循环,还有微任务和宏任务的执行顺序,先看一下他们的关系图
我们在开篇的时候说到,整体的代码script就是一个宏任务,所以我们一开始是从一个宏任务开始第一次循环,遇到宏任务里面的微任务就先执行微任务,宏任务放到宏任务的队列,执行完所有的微任务之后,再继续执行下一个宏任务,一直循环到所有任务执行完毕,这就是事件循环。
我们结合题目来具体分析一下
setTimeout(function(){
console.log(1)
},100)
new Promise(function(resolve){
console.log(2)
resolve()
console.log(3)
}).then(function(){
console.log(4)
new Promise((resolve,reject)=>{
console.log(5)
setTimeout(()=>{
console.log(6)
},10)
})
})
console.log(7)
console.log(8)
我们同样从上往下边看边分析:
1.执行整个script代码,也就是第一个宏任务,首先是setTimeout,属于异步宏任务,我们把它先挂到宏任务的队列
2.接下来是new Promise,首先执行里面的同步代码,打印出2,遇到resolve,加入微任务的队列
3.执行后面的同步代码,打印出3
4.接着执行script中的同步代码,打印出7和8
5.第一个宏任务执行完毕,此时先解决微任务队列里面的微任务,也就是resolve
6.打印出4,遇到new Promise,按顺序打印出5,遇到定时器,将其加入宏任务队列,此时宏任务队列有两个定时器
7.但是需要注意的是第一个定时器的时间是100ms,第二个定时器的时间是10ms,所以先执行第二个定时器,打印出6
8.此时微任务队列为空,继续执行宏任务队列,打印出1
9.所以,这道题的答案是2 3 7 8 4 5 6 1