简化一下你的代码:
Promise.resolve()
.then(() => {
setTimeout(() =>
console.info(1))
})
.then(() => {
Promise.resolve()
.then(() => {
setTimeout(() =>
console.info(2))
})
.then(() => {
setTimeout(() =>
console.info(5))
})
.then(() => {
setTimeout(() =>
console.info(6))
});
})
.then(() => {
setTimeout(() => {
console.info(3)
Promise.resolve().then(() =>
console.info(4))
})
})
进一步简化:
Promise.resolve()
.then(() => {
console.info(1)
})
.then(() => {
Promise.resolve()
.then(() => {
console.info(2)
})
.then(() => {
console.info(5)
})
.then(() => {
console.info(6)
});
})
.then(() => {
console.info(3)
//
console.info(4) // 1 2 3 4 5 6
// Promise.resolve().then(() =>
console.info(4)) // 1 2 3 5 4 6
// setTimeout(()=>
console.info(4)) // 1 2 3 5 6 4
})
你这个问题可以简化成和 React 没任何关系的问题,纯粹是浏览器任务队列的问题,Promise.resolve().then 可以生成一个微任务,setTimeout 或者你问题中的 setArr 生成的时宏任务(现代浏览器没得宏任务了,分成了更多任务列表,但是为了方便解释就还是说宏任务)
你可以观察进一步简化后的版本,以及看一下最后一个 then 里面关于不同方式 4 的输出时机,尝试理解一下。
但是不得不说,理解起来可能很困难,你需要了解 js 的事件循环以及队列优先级的问题。
大概来说,微任务优先级高,宏任务优先级低,每次事件循环按照优先级拿一遍任务。
- 最顶层的 Promise.resolve().then ->
console.info(1) 立刻输出 1
- 第二个 then
-- 第二个 then 里面 Promise.resolve().then ->
console.info(2) 立刻输出 2
-- 第二个 then 里面 Promise.resolve() 的第一个 then 作为一个微任务已经结束,后续的第二个 then 扔到下一次微任务队列中
- 第三个 then
-- 第一句
console.info(3) 立刻输出 3
-- 第二句:
--- 情况 1:
console.info(4) 那就立刻输出 4
--- 情况 2:Promise.resolve().then(() =>
console.info(4)) 扔一个微任务到下次事件队列,任务是
console.info(4)
--- 情况 3:setTimeout(()=>
console.info(4)) 扔一个宏任务到下次事件队列,任务是
console.info(4)
- 下一次循环
-- 微任务队列有一个
.then(() => {
console.info(5)
})
.then(() => {
console.info(6)
});
--- 这里你可以看成一个新的
Promise.resolve()
.then(()=>
console.info(5))
.then(() => {
console.info(6)
});
(当然这不能真的这么看,但是为了讲解方便,你就这么理解好了。。。)
--- 所以立即执行了这个微任务
console.info(5),把
console.info(6) 继续扔到微任务队列
-- 如果上一次循环的第三个 then 里面情况 2 ,那么在上一步的 5 输出结束后 6 的前面就有一个上一次扔过来的的微任务:
console.info(4)
-- 如果上一次是情况 3 ,输出 4 这个任务在宏任务队列,那么就先不管他,把当前下一个微任务输出 6 执行,再去执行宏任务队列
console.info(4)
单纯文字讲的讲不清楚,你要实际用代码多试几次,尽可能简化代码并且尝试不同的 case ,才能大概理解这个幺蛾子事件队列
顺便感谢你提供一道好玩的面试题,下次可以拿去为难其他人(