事件循环
循环,指的就是不断地查看 js 执行栈是否为空。
js 执行栈为空,意味着旧循环的结束,新一轮循环的开始。
一轮事件循环是这样的:
- js 执行栈为空
- 会先将微任务队列中的所有微任务填入 js 执行栈中。(在这个过程中如果有新的微任务,那么也会不断的放入 js 执行栈中执行。)
- 微任务队列为空
- 只将宏任务中的一个任务放入 js 执行栈。
- js 执行栈执行完宏任务后,再次为空
- 重复步骤一。
宏任务和微任务有哪些
宏任务
- setTimeout
- setInterval
- setImmediate (Node 独有)
- requestAnimationFrame (浏览器独有)
- I/O
- UI rendering (浏览器独有)
微任务
- Promises 返回的 then
- process.nextTick (Node 独有)
- Object.observe
- MutationObserver
- queueMicrotask
宏任务和微任务的区别只需要记住:执行一个宏任务前,要求微任务队列为空。
实战练习:log 输出顺序
题目内容直接从网络随便找的:22道js输出顺序问题,你能做出几道。
方法:用草稿纸记录 js 栈、微任务队列和宏任务队列的内容变化。遇到嵌套时,将前提内的所有输出写到一块,等当前一轮循环结束后再处理。
下面案例中,最终输出的数字都是连续的,毕竟这是学习,不是面试。
小试牛刀:
console.log(1)
setTimeout(()=>{
console.log(5)
},0)
process.nextTick(()=>{
console.log(3)
})
new Promise((resolve)=>{
console.log(2)
resolve()
}).then(()=>{
console.log(4)
})
开始嵌套:
console.log('1')
setTimeout(() => {
console.log('6')
}, 0)
process.nextTick(() => {
console.log('4')
})
new Promise((resolve) => {
console.log('2')
resolve()
}).then(() => {
console.log('5')
})
setTimeout(() => {
console.log('7')
}, 0)
new Promise((resolve) => {
console.log('3')
// 虽然这个 setTimeout 写在这里面
// 但它本质上和外面的 setTimeout 没区别
setTimeout(() => {
console.log('8')
// 该 setTimeout 的作用仅仅只是将 then
// 的回调延迟了一轮循环罢了
resolve()
}, 0)
}).then(() => {
console.log('9')
setTimeout(() => {
console.log('10')
new Promise((resolve) => {
console.log('11')
resolve()
}).then(() => {
console.log('12')
})
}, 0)
})
再次嵌套:
console.log(1)
setTimeout(function () {
console.log(5)
process.nextTick(function () {
console.log(7)
})
new Promise(function (resolve) {
console.log(6)
resolve()
}).then(function () {
console.log(8)
})
})
process.nextTick(function () {
console.log(3)
})
new Promise(function (resolve) {
console.log(2)
resolve()
}).then(function () {
console.log(4)
})
setTimeout(function () {
console.log(9)
process.nextTick(function () {
console.log(11)
})
new Promise(function (resolve) {
console.log(10)
resolve()
}).then(function () {
console.log(12)
})
})
如果上面三个你都对了,那么后面的就很简单了:
setTimeout(() => {
console.log(2)
Promise.resolve().then(data => {
console.log(3)
})
}, 0)
setTimeout(() => {
console.log(4)
}, 0)
Promise.resolve().then(data => {
console.log(1)
})
前面的宏任务定时器都是直接就添加进宏任务队列。那么如果有延迟呢?简单,草稿纸上画一个新的区域,
用于计时就可以了!
console.log(1)
setTimeout(function () {
console.log(6)
}, 0)
setTimeout(function () {
console.log(7)
setTimeout(function () {
console.log(9)
})
Promise.resolve().then(function () {
console.log(8)
})
}, 200)
Promise.resolve().then(function () {
console.log(3)
}).then(function () {
console.log(5)
})
Promise.resolve().then(function () {
console.log(4)
})
console.log(2)
简单:
console.log(1)
setTimeout(function cb1() {
console.log(5)
}, 0)
new Promise(function (resolve, reject) {
console.log(2)
resolve()
}).then(function cb2() {
console.log(4)
})
console.log(3)
还是简单
console.log(1)
setTimeout(() => {
console.log(3)
new Promise(resolve => {
resolve()
}).then(() => {
console.log(4)
})
}, 0)
setTimeout(() => {
console.log(5)
}, 0)
console.log(2)
看样子难道是没有更难的题了?
console.log(1)
setTimeout(() => {
console.log(3)
new Promise(resolve => {
console.log(4)
resolve()
}).then(() => {
console.log(5)
})
}, 0)
setTimeout(() => {
console.log(6)
}, 0)
console.log(2)
现在我已经无需要草稿纸也能写出来了!
console.log(1)
setTimeout(function () {
console.log(5)
})
Promise.resolve().then(function () {
console.log(4)
})
console.log(2)
setTimeout(function () {
console.log(6)
Promise.resolve().then(function () {
console.log(7)
})
}, 500)
setTimeout(function () {
console.log(8)
setTimeout(function () {
console.log(10)
Promise.resolve().then(function () {
console.log(11)
})
}, 500)
Promise.resolve().then(function () {
console.log(9)
})
}, 600)
console.log(3)
还是简单
function test() {
console.log('1')
setTimeout(function () {
console.log('7')
}, 1000)
}
test()
setTimeout(function () {
console.log('4')
})
new Promise(function (resolve) {
console.log('2')
setTimeout(function () {
console.log('6')
}, 100)
resolve()
}).then(function () {
setTimeout(function () {
console.log('5')
}, 0)
console.log('3')
})
console.log('3')
简单,只是把数字换成变量罢了
setTimeout(() => {
console.log(4)
}, 0)
new Promise((resolve, reject) => {
console.log(1)
resolve(3)
}).then(val => {
console.log(val)
})
console.log(2)
加了 for 循环罢了, 有啥区别吗?还是简单!
setTimeout(() => {
console.log(9)
}, 0)
for (let i = 1; i <= 3; i++) {
console.log(i)
}
console.log(4)
setTimeout(() => {
console.log(10)
}, 0)
for (let i = 5; i <= 7; i++) {
console.log(i)
}
console.log(8)
终于错了一次:重点在于 2 的输出为什么会那么早。原因是 async 函数的异步,指的是返回值是异步获取的。这一点类似于 Promise() 中的回调函数类似。
console.log(1)
async function async1() {
await async2()
console.log(5)
}
async function async2() {
console.log(2)
}
async1()
setTimeout(() => {
console.log(8)
}, 0)
new Promise(resolve => {
console.log(3)
resolve()
})
.then(() => {
console.log(6)
})
.then(() => {
console.log(7)
})
console.log(4)
如果将上面代码中的 await 删除,那么 async 关键字的作用就仅仅只有迷惑作用了,完全可以将其删除:
console.log(1)
async function async1() {
async2()
console.log(3)
}
async function async2() {
console.log(2)
}
async1()
setTimeout(() => {
console.log(8)
}, 0)
new Promise(resolve => {
console.log(4)
resolve()
})
.then(() => {
console.log(6)
})
.then(() => {
console.log(7)
})
console.log(5)
简单
console.log(1)
function a() {
return new Promise(resolve => {
console.log(2)
setTimeout(() => {
console.log(4)
}, 0)
resolve()
})
}
a().then(() => {
console.log(3)
})
简单
console.log(1)
setTimeout(function () {
console.log(5)
}, 0)
Promise.resolve().then(function () {
console.log(3)
}).then(function () {
console.log(4)
})
console.log(2)
简单
console.log(1)
setTimeout(function () {
console.log(5)
}, 10)
new Promise(resolve => {
console.log(2)
resolve()
setTimeout(() => console.log(6), 10)
}).then(function () {
console.log(4)
})
console.log(3)
太简单
console.log(1)
setTimeout(function () {
console.log(3)
}, 0)
setTimeout(function () {
console.log(4)
}, 0)
console.log(2)
太太简单
function fun1() {
console.log(2)
}
function fun2() {
console.log(1)
fun1()
console.log(3)
}
fun2()
没啥区别,还是简单
function func1() {
console.log(1)
}
function func2() {
setTimeout(() => {
console.log(3)
}, 0)
func1()
console.log(2)
}
func2()
简单。
// 注意,等号的右值是会执行的,别做多题,给做懵了
var p = new Promise(resolve => {
console.log(1)
resolve(4)
})
function func1() {
console.log(2)
}
function func2() {
setTimeout(() => {
console.log(5)
}, 0)
func1()
console.log(3)
p.then(resolve => {
console.log(resolve)
})
}
func2()
不能说难,因为我需要用到草稿纸。
console.log(1)
const interval = setInterval(() => {
console.log('4-9')
}, 0)
setTimeout(() => {
console.log(5)
Promise.resolve()
.then(() => {
console.log(7)
})
.then(() => {
console.log(8)
})
.then(() => {
setTimeout(() => {
console.log(10)
Promise.resolve()
.then(() => {
console.log(11)
})
.then(() => {
console.log(12)
})
.then(() => {
console.log(13)
clearInterval(interval)
})
}, 0)
})
console.log(6)
}, 0)
Promise.resolve().then(() => {
console.log(2)
}).then(() => {
console.log(3)
})
看到前面说了那么多简单,你可能会疑惑,难道我就没遇到过特别难的吗?
有!那就是在多个不同 then 链,而且一个有返回 resolve,一个没有返回的题目。
这类题目我只知道它会多延迟了一轮。
先看看常规的:
console.log(1);
Promise.resolve().then(() => {
console.log(3)
}).then(() => {
console.log(5)
}).then(() => {
console.log(7)
}).then(() => {
console.log(9)
})
Promise.resolve().then(() => {
console.log(4)
}).then(() => {
console.log(6)
}).then(() => {
console.log(8)
}).then(() => {
console.log(10)
})
console.log(2);
然后先简单变一下,现在还没上主菜
console.log(1);
Promise.resolve()
.then(() => Promise.resolve())
.then(() => console.log('4'))
Promise.resolve()
.then(() => {Promise.resolve()})
.then(() => console.log('3'))
console.log(2);
好了,前面两道题你很容易作对,但下面这道题你可能就懵了!
讲真,我也懵,虽然看过后我能答出来,但我解释不出。有兴趣的话可以查看 promise-order-invocation
唯一能想到的就是,它和 setTimeout 的延迟一样,最小是 4ms。
Promise.resolve()
.then(() => {
console.log(1)
return Promise.resolve()
}).then(() => {
console.log(5)
})
Promise.resolve()
.then(() => {
console.log(2)
}).then(() => {
console.log(3) // 这里比 5 提前输出,相信很容易理解
}).then(() => {
console.log(4) // 但重点在于,这里也会比 5 提前输出!
}).then(() => {
console.log(6)
})
再来一道题烧烧脑子
console.log(1);
Promise.resolve().then(() => {
console.log(3)
return Promise.resolve()
}).then(() => {
console.log(6)
return Promise.resolve()
}).then(() => {
console.log(8)
})
Promise.resolve().then(() => {
console.log(4)
}).then(() => {
console.log(5)
return Promise.resolve()
}).then(() => {
console.log(7)
})
console.log(2);
最后考考眼力,然后本篇文章就结束了
Promise.resolve()
.then(() => Promise.resolve())
.then(() => {Promise.resolve()})
.then(() => {Promise.resolve()})
.then(() => {
console.log(2)
})
Promise.resolve()
.then(() => {Promise.resolve()})
.then(() => {Promise.resolve()})
.then(() => {Promise.resolve()})
.then(() => {
console.log(1)
})