执行栈和任务队列
执行栈:在同步代码在执行时,会推入到一个执行栈中,然后依次执行里面的函数。
任务队列:在碰到异步任务时就交给其他线程去处理,异步任务完成后,会把回调放入任务队列中等待执行栈来取出执行。等当前执行栈里面所有同步任务执行完成后,就会去任务队列里面取出回调放到执行栈里面执行。
事件循环:通过不断循环去任务队列里面取出异步回调来执行,这个过程就是事件循环,而每一次循环就是一个事件周期或称为一次 tick。
宏任务和微任务
任务队列又分为宏任务队列和微任务队列。
执行栈在同步代码执行完成后,优先检查微任务队列是否有任务需要执行,如果没有,再去宏任务队列检查是否有任务执行,如此往复。
宏任务特征:
- 需要异步线程支持;
- 有明确的异步任务需要执行和回调(比如http请求或者定时器)
微任务特征:
- 不要要其他异步线程支持;
- 没有明确的异步任务需要执行,只有回调;
常见宏任务:
- setTimeout()
- setInterval()
- setImmediate()
常见微任务:
- promise.then()、promise.catch()、await(后面)
- new MutaionObserver()
- process.nextTick()
定时器误差
只有当主线程的同步任务执行完后,才会去任务队列里面取回调来执行。视图更新
微任务队列执行完成后,也就是一次事件循环结束后,浏览器会执行视图渲染,当然这里会有浏览器的优化,可能会合并多次循环的结果做一次视图重绘,因此视图更新是在事件循环之后,所以并不是每一次操作 Dom 都一定会立马刷新视图。
实战
每次执行一个宏任务,然后执行所有微任务(宏任务队列里面的任务是一个一个执行的,微任务队列是一队一队执行的)
setTimeout(function() {
console.log('4')
})
new Promise(function(resolve) {
console.log('1')
resolve()
}).then(function() {
console.log('3')
})
console.log('2')
// 输出 1 2 3 4
setTimeout(function () {
console.log('6')
}, 0)
console.log('1')
async function async1() {
console.log('2')
await async2()
console.log('5')
}
async function async2() {
console.log('3')
}
async1()
console.log('4')
// 输出 1 2 3 4 5 6
console.log("start");
setTimeout(function () {
console.log("setTimeout1");
const promise2 = new Promise((resolve, reject) => {
console.log("promise2");
resolve();
});
promise2.then(() => {
console.log("then2");
const promise3 = new Promise((resolve) => {
console.log("promise3");
resolve();
});
promise3.then(() => {
console.log("then3");
});
});
}, 1000);
setTimeout(function () {
console.log("setTimeout2");
const promise4 = new Promise((resolve, reject) => {
console.log("promise4");
resolve();
});
promise4.then(() => {
console.log("then4");
});
}, 1000);
const promise1 = new Promise((resolve) => {
console.log("promise1");
resolve();
});
promise1.then(() => {
console.log("then1");
});
console.log("end");
// 输出 start promise1 end then1 setTimeout1 promise2 then2 promise3 then3 setTimeout2 promise4 then4