1. Event Loop (事件循环)
1.1 Event Queue (任务队列)
所有的任务可以分为同步任务和异步任务,同步任务,顾名思义,就是立即执行的任务,同步任务一般会直接进入到主线程中执行;而异步任务,就是异步执行的任务,比如ajax网络请求,setTimeout 定时函数等都属于异步任务,异步任务会通过Event Queue (任务队列)
的机制来进行协调。
同步和异步任务分别进入不同的执行环境,同步的进入主线程,即主执行栈
,异步的进入 Event Queue (任务队列)
。主线程内的任务执行完毕为空,会去 Event Queue (任务队列)
读取对应的任务,推入主线程执行。 上述过程的不断重复就是我们说的 Event Loop (事件循环)
。
- 看个简单的例子
console.log('代码开始执行');
setTimeout(function(){
console.log('定时器开始执行')
});
console.log('代码执行结束');
//结果是: 代码开始执行->代码执行结束->定时器开始啦
//因为setTimeout是异步任务,所以就会后后执行,
- 在看一个例子
setTimeout(function(){
console.log('2')
});
for(let i =0;i<3000;i++){
console.log(1);
}
console.log(3);
//输出3000个1之后,在输出3,然后才输出2
//这也就说明先执行同步任务,等到同步任务执行完后,然后再到任务队列中去执行异步任务
1.2 Macro Task (宏任务)和 Micro Task(微任务)
任务队列还可以分成宏任务
与微任务
,微任务
就是一个跟屁虫,一直跟在当前宏任务
后面,代码执行到一个微任务
,一个接着一个。
我们常见的宏任务有:script(整体代码)、setTimeout、setInterval、I/O、UI 交互事件、setImmediate(Node.js 环境),ajax,读取文件
微任务有:Promise、MutationObserver、process.nextTick(Node.js 环境);
优先级 process.nextTick > promise.then > setTimeout > setImmediate
2. 经典例题
console.log('script start')
async function async1 () {
console.log('async1'); //同步
await async2()
console.log('async1 end') //异步
}
async function async2 () {
console.log('async2 end')
}
async1()
setTimeout(function () {
console.log('setTimeout')
}, 0)
process.nextTick(() => {
console.error('nextTick')
})
new Promise(resolve => {
console.log('Promise') //同步
resolve()
}).then(function () {
console.log('promise1')
}).then(function () {
console.log('promise2')
})
console.log('script end')
首先执行同步代码,输出script start
调用async1 输出async1
async1内同步调用async2 输出async2 end
并产生微任务1
遇到setTimeout,产生一个宏任务
遇到process.nextTick 产生 微任务2
执行Promise,输出Promise
.then产生 微任务3 和 微任务4
接着输出script end
同步任务都执行完了,然后去到异步任务队列 (此时会输出一个无运行结果的undefined
)
开始执行当前宏任务产生的微任务队列
微任务优先级process.nextTick最高 所以微任务执行顺序为2134
微任务2 process.nextTick的nextTick
微任务1 async1 end
微任务3 promise1
微任务4 promise2
最后,执行下一个宏任务,即执行setTimeout,输出setTimeout
-
node环境输出结果:
script start async1 async2 end Promise script end *undefined* > nextTick async1 end promise1 promise2 setTimeout
3. 总结
- 首先执行同步代码,
- 当执行完所有的同步代码后,执行栈为空,去查询是否有异步代码需要执行
- 执行所有的微任务
- 当执行完所有的微任务后
- 开始下一轮Event-loop,执行宏任务中的异步代码
最后的最后,记住,JavaScript 是一门单线程语言,异步操作都是放到事件循环队列里面,等待主执行栈来执行的,并没有专门的异步执行线程。