js语言的一大特短就是单线程,即同一时间只能做一件事情。
1、事件循环
js代码在执行过程中会有很多任务,这些任务总的分为两类:同步任务和异步任务。异步任务又可以细分为宏任务与微任务。
2、执行顺序
主线程 > 微任务 > Dom渲染 > 宏任务
- 首先js 是单线程运行的,在代码执行的时候,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。
- 在执行同步代码的时候,如果遇到了异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。
- 当同步事件执行完毕后,再将异步事件对应的回调加入到与当前执行栈中不同的另一个任务队列中等待执行。
- 任务队列可以分为宏任务对列和微任务对列,当当前执行栈中的事件执行完毕后,js 引擎首先会判断微任务对列中是否有任务可以执行,如果有就将微任务队首的事件压入栈中执行。
- 当微任务对列中的任务都执行完成后再去判断宏任务对列中的任务。
3、微任务
promise中的三个方法(then、catch、finally)、node中的process.nextTick、对DOM监听变化的MutationObserver…
4、宏任务
script、setTimeout、setInterval、setImmediate、I/O、UI渲染等…
宏任务、微任务都可以异步操作,那微任务要解决的是什么问题?
为了解决主线程任务过多的时候,异步回调等待时间过长的问题,可以在实时性和效率之间做一个有效的权衡。
//主线程
console.log(1);
//异步---宏任务
setTimeout(() => {
console.log(2);
})
//异步---微任务
new Promise((resolve, reject) => {
resolve()
console.log(3);
}).then(() => {
console.log(4);
}).finally(() => {
console.log(5);
})
//主线程
console.log(6);
上述代码执行结果:1 3 6 4 5 2
setTimeout(function() {
console.log(1)
}, 0);
new Promise(function(resolve, reject) {
console.log(2);
resolve()
}).then(function() {
console.log(3)
});
process.nextTick(function () {
console.log(4)
})
console.log(5)
上述代码执行结果:2 5 4 3 1
- 主线程开始执行,遇到setTimeout,将setTimeout的回调函数丢到宏任务队列中;
- 在往下执行new Promise立即执行,输出2,then的回调函数丢到微任务队列中;
- 再继续执行,遇到process.nextTick,同样将回调函数扔到为任务队列;
- 再继续执行,输出5;
- 当所有同步任务执行完成后看有没有可以执行的微任务,发现有then函数和nextTick两个微任务,先执行哪个呢?process.nextTick指定的异步任务总是发生在所有异步任务之前,因此先执行process.nextTick输出4;
- 然后执行then函数输出3,第一轮执行结束。
- 第二轮:从宏任务队列开始,发现setTimeout回调,输出1执行完毕,因此结果是25431;
new Promise((resolve) => {
resolve(6);
}).then((res) => {
console.log(res);
});
console.log("1");
async function async1() {
await async2();
console.log("2");
}
async function async2() {
console.log("3");
}
async1();
setTimeout(function () {
console.log("setTIMEOUT");
}, 0);
new Promise((resolve) => {
console.log("Promise");
resolve(5);
}).then().then((res) => {
console.log("promise2");
console.log(res);
});
process.nextTick(function () {
console.log("nextTick")
})
上述代码执行结果:1 3 Promise nextTick 6 2 promise2 5 setTIMEOUT