因为js是单线程语言,当遇到异步任务(如ajax操作等)时,不可能一直等待异步完成,再继续往下执行,在这期间浏览器是空闲状态,显而易见这会导致巨大的资源浪费。
执行栈
当执行某个函数、用户点击一次鼠标,Ajax完成,一个图片加载完成等事件发生时,只要指定过回调函数,这些事件发生时就会进入执行栈队列中,等待主线程读取,遵循先进先出原则。
主线程
要明确的一点是,主线程跟执行栈是不同概念,主线程规定现在执行执行栈中的哪个事件。
主线程循环:即主线程会不停的从执行栈中读取事件,会执行完所有栈中的同步代码。
当遇到一个异步事件后,并不会一直等待异步事件返回结果,而是会将这个事件挂在与执行栈不同的队列中,我们称之为任务队列(Task Queue)。
当主线程将执行栈中所有的代码执行完之后,主线程将会去查看任务队列是否有任务。如果有,那么主线程会依次执行那些任务队列中的回调函数。
全局范围内的js代码、new Promise、Promise.then、Promise.all().then()、process.nextTick的执行顺序
在这里面由于new Promise会立即执行,这里面的代码会按照顺序依次执行。相应的顺序为:有顺序的js代码 --> new Promise --> 有顺序的js代码(全局js和new Promise执行完毕之后) --> 所有的process.nextTick --> Promise().then() --> Promise.all().then()
console.log(1);// 顺序-1
Promise.all([new Promise(resolve => {
console.log(2);// 顺序-2
resolve(100);
})]).then(values => {
console.log(values);// 顺序- 10
});
process.nextTick(() => {
console.log(6);// 顺序- 6
});
new Promise((resolve) => {
console.log(3);// 顺序-3
resolve('3-1');
}).then(values => {
console.log(values);// 顺序- 8
});
console.log(4);// 顺序- 4
new Promise((resolve) => {
console.log(5);// 顺序- 5
resolve('5-1');
}).then(values => {
console.log(values);// 顺序- 9
})
process.nextTick(() => {
console.log(7);// 顺序- 7
})
涉及到setTimeout,以及定时器里面有new Promise、Promise.then、Promise.all的情况
这种情况的执行顺序是在第一种情况都执行完毕之后才会执行。执行顺序为:
多个定时器时间相同:按照顺序执行setTimeout。定时器里的执行顺序为,顺序执行js代码 --> new Promise --> 有顺序的js代码(js和new Promise执行完毕之后) --> 所有的process.nextTick --> Promise().then() --> Promise.all().then()。这实际就是重复第一种情况中的执行顺序。
setTimeout(() => {
console.log("setTimeout1");// 顺序-- 1
new Promise((resolve) => {
console.log('setTimeout1 --- Promise');// 顺序-- 2
resolve('Promise-1');
}).then((values) => {
console.log(values);// 顺序-- 5
});
}, 0)
setTimeout(() => {
console.log("setTimeout2");// 顺序-- 3
new Promise((resolve) => {
console.log('setTimeout2 --- Promise');// 顺序-- 4
resolve('Promise-2');
}).then((values) => {
console.log(values);// 顺序-- 6
});
}, 0)
宏任务和微任务
对于消息队列中的任务,分为两类:
- 宏任务(macrotask ):setTimeout、setInterval
- 微任务(microtask):promise
宏任务中包含了微任务,一个宏任务执行完成,开始下一个宏任务。
console.log("a")
let r = new Promise(function (resolve, reject) {
console.log("b");
resolve()
});
r.then(() => console.log("c"));
setTimeout(() => { console.log("d") }, 0)
setTimeout(() => { console.log("e") }, 1000)
console.log("f")
看这几行代码
- 开始执行,发现了一些代码,放进一个宏任务中
- 先执行a
- 执行b
- 发现异步操作then,属于微任务,把他添加进当前宏任务中的微任务的队列
- 发现定时器d,setTimeout属于宏任务,添加到宏任务的队列
- 发现定时器e,setTimeout属于宏任务,添加到宏任务的队列
- 执行f
- 代码都已经执行完成,开始执行微任务队列,打印出c
- 微任务队列执行完成,开始执行下一个宏任务队列
- 直接打印出d,没有其他微任务,开始执行下一个宏任务队列
- 打印出e,所有宏任务执行完成