# 任务队列
JS 是单线程的,这就意味着所有的任务需要排队执行,这就是任务队列。
# 同步任务、异步任务
为了防止主线程阻塞,JS 又分为同步任务和异步任务。
同步任务指的就是那些按顺序执行的任务。同步任务会直接进入主线程执行,只有前面一个任务执行完才会执行下一个任务。
异步任务指的就是那些需要等待的任务。异步任务会在Event Table中注册相应的回调函数,当指定的步骤完成了,Event Table会将注册了的回调函数推送到Event Queue。当主线程内的任务执行完毕为空之后,就会到Event Queue读取相应的函数,进入主线程执行。

# 宏任务、微任务
JS 是单线程的,为了更好的处理高优先级的任务,防止阻塞,需要将任务划分为宏任务和微任务。
一般宿主环境提供的异步方法都是宏任务,常见的宏任务有:
| # | 浏览器 | Node |
|---|---|---|
| I/O | ✅ | ✅ |
| script代码块 | ✅ | ❌ |
| setTimeout | ✅ | ✅ |
| setInterval | ✅ | ✅ |
| setImmediate | ❌ | ✅ |
| requestAnimationFrame | ✅ | ❌ |
常见的微任务有:
| # | 浏览器 | Node |
|---|---|---|
| process.nextTick | ❌ | ✅ |
| MutationObserver | ✅ | ❌ |
| Promise.then catch finally | ✅ | ✅ |
# 事件循环

执行完成后一个宏任务后,会把当前的微任务队列全部执行完毕,然后执行GUI渲染,再从宏任务队列中取出一个宏任务,放到执行栈中执行。这个宏任务执行完毕后,再去清空微任务,依次循环,直到所有代码执行完。
# 微任务和GUI渲染
<script>
document.body.style.background = 'red';
console.log(1)
Promise.resolve().then(()=>{
console.log(2)
document.body.style.background = 'yellow';
})
console.log(3);
</script>
上述代码执行结果为
1
3
2
页面最终渲染为黄色,并且不会显示绿色
但是如果把代码改造如下
<script>
document.body.style.background = 'red';
console.log(1)
setTimeout(() => {
console.log(2)
document.body.style.background = 'yellow';
}, 1000)
console.log(3);
</script>
输出结果为
1
3
2
页面先显示为红色,隔一秒后会显示成绿色
# 事件任务
<script>
button.addEventListener('click',()=>{
console.log('listener1');
Promise.resolve().then(()=>console.log('micro task1'))
})
button.addEventListener('click',()=>{
console.log('listener2');
Promise.resolve().then(()=>console.log('micro task2'))
})
button.click(); // click1() click2()
</script>
输出结果
listener1
listener2
micro task1
micro task2
由于click不需要用户点击,click事件是立即执行的,相当于不用放到宏任务队列里,上述代码可以简化如下:
console.log('listener1');
Promise.resolve().then(()=>console.log('micro task1'))
console.log('listener2');
Promise.resolve().then(()=>console.log('micro task2'))
# 定时器任务
<script>
Promise.resolve().then(() => {
console.log('Promise1')
setTimeout(() => {
console.log('setTimeout2')
}, 0);
})
setTimeout(() => {
console.log('setTimeout1');
Promise.resolve().then(() => {
console.log('Promise2')
})
}, 0);
</script>
输出结果
Promise1
setTimeout1
Promise2
setTimeout2
# 实战题
async function async1 () {
console.log('async1 start');
/*
下面两句相当于下面这样
await async2().then(console.log('async1 end'))
*/
await async2();
console.log('async1 end')
/*
在node环境下运行的结果是不同的,因为浏览器可以识别出async2是个promsie,但node识别不出来,所以会用一个promise包装下ansync2(),如果内部发现resolve的值是一个promise,就会调用这个promise的then方法,当这个promise完成后,后面还有一个then
new Promise((resolve, reject) => resolve(async2())).then(() => {
console.log('async1 end')
})
*/
}
async function async2 () {
console.log('async2')
}
console.log('script start')
setTimeout(() => {
console.log('setTimeout');
}, 0);
async1()
new Promise((resolve) => {
console.log('promise1');
resolve()
}).then(()=>console.log('promise2'))
console.log('script end')
输出结果
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
下面我们再来一道练手题:
console.log(1);
async function async () {
console.log(2);
await console.log(3);
console.log(4)
}
setTimeout(() => {
console.log(5);
}, 0);
const promise = new Promise((resolve, reject) => {
console.log(6);
resolve(7)
})
promise.then(res => {
console.log(res)
})
async ();
console.log(8);
输出结果
1,6,2,3, 8, 7, 4, 5
你们把上面的题做一个小小的修改来看一下它的输出结果。
console.log(1);
async function async () {
console.log(2);
const a = await 3;
console.log(a)
console.log(4)
}
setTimeout(() => {
console.log(5);
}, 0);
const promise = new Promise((resolve, reject) => {
console.log(6);
resolve(7)
})
promise.then(res => {
console.log(res)
})
async ();
console.log(8);
输出结果
1,6,2,8,7,3,4,5
async返回的是一个promise,内部实现原理是generator+co库
await 相当于yield,如果产出的是一个promise会调用这个promise的then方法
参考: