new Promise(resolve => {
setTimeout(()=>{
console.log(666);
new Promise(resolve => {
resolve();
})
.then(() => {console.log(777);})
})
resolve();
})
.then(() => {
new Promise(resolve => {
resolve();
})
.then(() => {console.log(111);})
.then(() => {console.log(222);});
})
.then(() => {
new Promise((resolve) => {
resolve()
})
.then(() => {
new Promise((resolve) => {
resolve()
})
.then(() => {console.log(444)})
})
.then(() => {
console.log(555);
})
})
.then(() => {
console.log(333);
})
111
222
333
444
555
666
777
如果你没有得出正确的结果,有必要继续往下看.
为了能正确解答上题,需要对宏任务、微任务以及Event-Loop深入理解.
浏览器执行代码的过程中,JS引擎会将大部分代码进行分类,分别分到这两个队列中–宏任务(macrotask ) 和 微任务(microtask ) .
常见的宏任务:script(整体代码), XHR回调,setTimeout, setInterval, setImmediate(node独有), I/O.
上面的描述仍然有些生涩,下面借助案例深入理解.
app.js
setTimeout(()=>{ //宏任务2
console.log(2);
},0)
setTimeout(()=>{ //宏任务3
console.log(3);
},0)
console.log(1);
执行结果: 1 – 2 – 3
浏览器里面包含有很多个线程,js是单线程,它跑在js引擎中.而定时器,ajax请求都是在不同的线程里执行.比如定时器线程执行完毕,它会将回调函数放到宏任务队列中.ajax请求也如此,它会单独使用一个线程发起ajax请求,请求成功后将回调函数放入宏任务队列中.这里需要格外指出,ajax请求和定时器本身的延迟功能都不能算是宏任务,当ajax请求以及定时器都执行完毕后,它们的回调函数才会放入宏任务队列里等待执行.
微任务是宏任务的组成部分,微任务与宏任务是包含关系,并非前后并列.如果要谈微任务,需要指出它属于哪个宏任务才有意义.
常见的宏任务:process.nextTick(nodejs端),Promise等.
app.js
console.log(1);
new Promise((resolve)=>{
resolve();
}).then(()=>{
console.log(2)
})
console.log(3)
执行结果: 1 – 3 – 2
宏任务由宿主环境开启,与此相对应,微任务是 js 引擎从代码层面开启的.
如果还对宏任务和微任务的关系模棱两可,下面从 Event-Loop 角度详细阐述.
从上图可知,宏任务形成了一个拥有先后顺序的队列.每个宏任务中分为同步代码和微任务队列.
console.log(1);
document.getElementById("div").style.color = "red";
console.log(2);
在实践中发现,当上面代码执行到第三行时,控制台输出了1
并且dom
结构发生了变化.但要引起注意,dom
树更新不代表浏览器已经渲染完了,浏览器只会在微任务队列全部执行完毕以后才开始渲染页面.
dom操作它既不能算宏任务也不能算微任务,它应该归于同步代码执行的范畴.
setTimeout(() => {
console.log("11111")
}, 0)
requestAnimationFrame(() => {
console.log("22222")
})
new Promise(resolve => {
console.log('promise');
resolve();
})
.then(() => {console.log('then')})
执行结果: promise – then – 22222 – 11111
很多人会把 requestAnimationFrame 归结到宏任务中,因为发现它会在微任务队列完成后执行.
但实际上 requestAnimationFrame 它既不能算宏任务,也并非是微任务.它的执行时机是在当前宏任务范围内,执行完同步代码和微任务队列后再执行.它仍然属于宏任务范围内,但是是在微任务队列执行完毕后才执行.
new Promise((resolve)=>{
console.log(1);
resolve();
}).then(()=>{
console.log(2);
})
new Promise里面的包裹的函数,也就是输出1的那段代码是同步执行的.而then包裹的函数才会被加载到微任务队列中等待执行.
new Promise((resolve)=>{
console.log(1)
resolve();
}).then(()=>{
console.log(2);
}).then(()=>{
console.log(3);
}).then(()=>{
console.log(4);
})
执行结果: 1 – 2 – 3 – 4
在平时开发中,在Promise链中通常会返回一个新的Promise做异步操作返回相应的值.如下.
new Promise((resolve)=>{
console.log(1)
resolve();
}).then(()=>{
return new Promise((resolve)=>{
resolve(2)
})
}).then((n)=>{
console.log(n);
})
执行结果: 1 – 2
但上述代码中,then函数的回调里没有返回任何东西.但是后续then包含的回调函数仍然会依次执行,返回 1 – 2 – 3 – 4.并且它可以在末尾无限接then函数,这些函数也都会依次执行.
new Promise((resolve)=>{ // 1
console.log("a") // 2
resolve(); // 3
}).then(()=>{ // 4
console.log("b"); // 5
}).then(()=>{ // 6
console.log("c"); // 7
}) // 8
console.log("d") // 9
执行结果: a – d – b – c
有了以上知识的储备再回到本文最初的面试题,这道题就可以轻松解决了.(为了方便阐述,加入右边行号)
new Promise(resolve => { // 1
setTimeout(()=>{ // 2
console.log(666); // 3
new Promise(resolve => { // 4
resolve();
})
.then(() => {console.log(777);}) // 7
})
resolve(); // 9
}) // 10
.then(() => { // 11
new Promise(resolve => { // 12
resolve(); // 13
})
.then(() => {console.log(111);}) // 15
.then(() => {console.log(222);}); // 16
}) // 17
.then(() => { // 18
new Promise((resolve) => { // 19
resolve()
})
.then(() => { // 22
new Promise((resolve) => { // 23
resolve()
})
.then(() => {console.log(444)}) // 26
})
.then(() => { // 28
console.log(555); // 29
})
})
.then(() => { // 32
console.log(333);
})
综上所述:输出分别为 111 – 222 – 333 – 444 – 555 – 666 – 777