参考文章:
- 10分钟理解JS引擎的执行机制
- JS事件循环机制(event loop)之宏任务/微任务
- JavaScript 运行机制详解:再谈Event Loop
首选要知道两点
- JavaScript 是单线程语言
- Event Loop 是 JavaScript 的执行机制
单线程如何实现异步?
通过事件循环(event loop)
event loop
js 是单线程,可将任务分为两类:
- 同步任务
- 异步任务
先看一段代码:
console.log('script start'); //同步任务
setTimeout(function() { //异步任务,同步任务完成后,0秒后执行
console.log('setTimeout');
}, 0);
//Promise中的 .then()里的函数是异步任务
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end'); //同步任务
复制代码
答案是:script start, script end, promise1, promise2, setTimeout
为什么会这样的顺序? 如图:
解读:
- 同步任务进入主线程,异步任务进入Event Table 并注册函数
- 当指定的事情完成时,Event Table会将注册的回调函数移入 Event Queue
- 同步任务进入主线程后一直执行,直到主线程空闲时,才会去 Event Queue中查看是否有可执行的异步任务,如果有就推入主线程中
- 上述过程不断重复,即Event Loop;
微任务(Microtasks),宏任务(task)?
首先,微任务和宏任务都是异步任务,它们都属于一个队列,主要区别在于他们的执行顺序
- macro-task(宏任务):包括整体代码script,setTimeout,setInterval, setImmediate
- micro-task(微任务):原生Promise(有些实现的promise将then方法放到了宏任务中),process.nextTick,MutationObserver
综合分类方式:
- 执行一个宏任务,过程中如果遇到微任务,就将其放到微任务的【事件队列】里
- 当前宏任务执行完成后,会查看微任务的【事件队列】,并将里面全局的微任务依次执行完
好多栗子
栗子1:
//同步任务,放入主线程里
console.log(1);
//异步任务,被放入 event table,0秒后被推入 event queue 里
setTimeout(function () {
console.log(2)
}, 0);
//同步任务,放入主线程里
console.log(3);
复制代码
所以打印顺序: 1,3,2
栗子2:
setTimeout(function(){
console.log('定时器开始')
});
new Promise(function(resolve){
console.log('马上执行for循环');
for(var i = 0; i < 10000; i++){
i == 99 && resolve();
}
}).then(function(){
console.log('执行then函数')
});
console.log('代码执行结束');
复制代码
- 顺序执行,遇到 setTimeout ,将其放到宏任务的 event queue,即
macrotasks=['setTImeout']
- 遇到 new Promise 直接执行, 输出 "马上执行for循环",并将then方法中的回调函数放入微任务的 event queue,即
microtasks=['then']
- 执行同步任务,输出 "代码执行结束"
- 第一轮事件循环结束,查看本轮的微任务,即
microtasks=['then']
, 输出"执行then函数" - 第二轮事件循环开始,先执行一个宏任务,即
macrotasks=['setTImeout']
, 输出"定时器开始" - 输出结果:马上执行for循环, 代码执行结束, 执行then函数, 定时器开始
栗子3:
setTimeout(() => {
cosnole.log('A');
},0)
var obj = {
func: function() {
setTimeout(function () {
console.log('B')
}, 0);
return new Promise(function () {
console.log('C');
resolve();
})
}
}
obj.func().then(function() {
console.log('D')
})
console.log('E')
// C E D A B
复制代码
- 顺序执行,setTimeout A 被加入宏任务的 event queue 中,此时
macrotasks=['A']
; - obj.fun()执行时,setTimeout B 被加入宏任务的 event queue 中,此时
macrotasks=['A','B']
; - 接着返回一个Promise对象,Promise新建后立即执行,输出‘C'
- 然后,then 方法中的回调函数被加入到微任务的 event queue 中,将在当前脚本所有同步任务执行完才会执行,此时
microtasks=['C']
- 然后执行同步任务,输出‘E'
- 同步任务执行完毕,检查微任务的 event queue,完成其中任务,输出‘D'
- 最后执行宏任务的event queue,按照入队列的时间顺序,输出’A‘,在输出’B‘
栗子4:
console.log('1');
//命名setTimeout1
setTimeout(function() {
console.log('2');
process.nextTick(function() { //命名process2
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() { //命名then2
console.log('5')
})
})
//命名process1
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() { //命名 then1
console.log('8')
})
//命名setTimeout2
setTimeout(function() {
console.log('9');
process.nextTick(function() { //命名process3
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() { //命名 then3
console.log('12')
})
})
复制代码
- 顺序执行,执行同步任务,输出‘1’,宏任务的event queue中放入 setTimeout1, 微任务的event queue中放入process1。即
macrotasks = ['setTimeout1']
,microtasks=['process1']
- 遇到Promise的同步任务,输出‘7’,并将then中的回调函数放入微任务的event queue中,即
microtasks=['process1', 'then1']
- 接着遇到setTimeout,宏任务的event queue中放入 setTimeout2,即
macrotasks = ['setTimeout1', 'setTimeout2']
, - 到此第一轮事件循环结束,输出’1、7‘,主线程空闲,去查看微任务的event queue中是否有任务,此时
microtasks=['process1', 'then1']
,所以输出’6、8‘。任务执行完成后,微任务的event queue清空,即microtasks = []
。 - 第二轮事件循环开始,去顺序执行宏任务的event queue中的任务,即执行 setTimeout1, 输出’2‘,紧接着将 process2 放入微任务的event queue 中,遇到 Promise 输出’4‘,并将 then2 放入微任务的event queue 中,即
microtasks=['process2', 'then2']
- 第二轮事件循环结束,主线程空闲,查看微任务的event queue, 此时
microtasks=['process2', 'then2']
,所以输出’3、5‘,任务执行完后,微任务的event queue清空,即microtasks = []
。 - 第三轮事件循环开始,查看宏任务的event queue,
macrotasks = ['setTimeout2']
,同理输出’9、11‘,此时的microtasks=['process3, 'then3']
。 - 第三轮事件循环结束,主线程空闲,查看微任务的event queue, 此时的
microtasks=['process3, 'then3']
,所以输出’10、12‘ - 运行结果顺序: 1,7,6,8,2,4,3,5,9,11,10,12
(...觉得自己好罗嗦)
栗子5:
async function async1 () {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2 () {
console.log('async2');
}
console.log('script start');
setTimeout(function () {
console.log('setTimeout');
}, 0);
async1();
new Promise(function (resolve) {
console.log('promise1');
resolve();
}).then(function () {
console.log('promise2');
});
console.log('script end');
//输出顺序:
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout
复制代码