javascript从诞生之日起就是一门单线程(setTimeout()的回调函数是在主线程执行的)的非阻塞(事件循环)的脚本语言。在同一个时间只能做一件事情,这就导致后面的任务需要等到前面的任务完成才能执行,如果前面的任务很耗时就会造成后面的任务一直等待。
- 同步任务:在主线程上排队执行的任务只有前一个任务执行完毕,才能执行后一个任务。
- 异步任务:不进入主线程,而是进入任务队列。
当主线程中的任务执行完毕,就从任务队列中取出任务放进主线程中来进行执行。由于主线程不断重复的获得任务、执行任务、再获取再执行,所以者种机制被叫做事件循环(Event Loop)
异步任务主要分为宏任务与微任务两种。ES6 规范中,宏任务(Macrotask) 称为 Task, 微任务(Microtask) 称为 Jobs。宏任务是由宿主(浏览器、Node)发起的,而微任务由 JS 自身发起的。事件循环的过程中,执行栈在同步代码执行完成后,优先检查微任务队列是否有任务需要执行,如果没有,再去宏任务队列检查是否有任务执行,如此往复。微任务一般在当前循环就会优先执行,而宏任务会等到下一次循环,因此,微任务一般比宏任务先执行,并且微任务队列只有一个,宏任务队列可能有多个。
- 宏任务:setTimeout、setInterval、Ajax、DOM事件、script整体代码块、I/OUI交互事件、postMessageMessage、Channel setImmediate(Node.js 环境)
- 微任务:Promise.[ then/catch/finally ]、async/await、Object.observe、MutationObserver(浏览器环境)、process.nextTick(Node环境)注意new Promise是同步执行的,和它的then()等方法区分。
举例1:
console.log('同步代码1'); 1,执行第一个同步任务
setTimeout(() => {
console.log('setTimeout') 5,执行宏任务
}, 0)
new Promise((resolve) => {
console.log('同步代码2') 2,执行第二个同步任务
resolve()
}).then(() => {
console.log('promise.then') 4,执行微任务
})
console.log('同步代码3'); 3,执行第三个同步任务
// 最终输出"同步代码1"、"同步代码2"、"同步代码3"、"promise.then"、"setTimeout"
顺便提一下,在浏览器中 setTimeout 的延时设置为 0 的话,会默认为 4ms,NodeJS 为 1ms。具体值可能不固定,但不是为 0。
注意:new Promise 是同步执行的,promise.then 里面的回调才是异步的。
举例2:
setTimeout(() => {
console.log('setTimeout start'); 5,执行宏任务里的第一个任务
new Promise((resolve) => {
console.log('promise1 start'); 6,执行宏任务里的第二个任务
resolve();
}).then(() => {
console.log('promise1 end'); 8,执行宏任务里的微任务
})
console.log('setTimeout end'); 7,执行宏任务里的第三个任务
}, 0);
function promise2() {
return new Promise((resolve) => {
console.log('promise2'); 3,执行第一个微任务
resolve();
})
}
async function async1() {
console.log('async1 start'); 1,执行第一个同步任务
await promise2();
console.log('async1 end'); 4,执行第二个微任务
}
async1();
console.log('script end'); 2,执行第二个同步任务
async/await 微任务可以理解为:当执行第一个同步任务时(调用async1函数),输出async1 start;当执行微任务时,将await关键字后面的全部视为微任务,依次输出为 promise2、async1 end。
async1 start
script end
promise2
async1 end
setTimeout start
promise1 start
setTimeout end
promise1 end
举例3:
// 宏任务队列 1
setTimeout(() => {
// 宏任务队列 2.1
console.log('timer_1');
setTimeout(() => {
// 宏任务队列 3
console.log('timer_3')
}, 0)
new Promise(resolve => {
resolve()
console.log('new promise')
}).then(() => {
// 微任务队列 1
console.log('promise then')
})
}, 0)
setTimeout(() => {
// 宏任务队列 2.2
console.log('timer_2')
}, 0)
console.log('========== Sync queue ==========')
父宏任务中嵌套子宏任务,先执行完第一个父宏任务外层的所有宏任务,最后执行嵌套的子微任务。所以先是time_2,后是time_3
1 timer_1
2 new promise
3 promise then
4 timer_2
5 timer_3
举例4:
async function async1() {
console.log('async1 start'); 2,第二个同步任务
await async2();
console.log('async1 end'); 5,第二个微任务
}
async function async2() {
console.log('async2'); 4,第一个微任务
}
console.log('script start'); 1,第一个同步任务
setTimeout(function() {
console.log('setTimeout'); 7,执行宏任务
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1'); 3,第三个同步任务
resolve();
}).then(function() {
console.log('promise2'); 6,第三个微任务
});
console.log('script end'); 4,第四个同步任务
script start
async1 start
promise1
script end
async2
async1 end
promise2
setTimeout