Event Loop
是计算机系统的一种运行机制。(是一个程序结构,用于等待发送消息和事件)。 在不同的地方有不同的实现。;浏览器和 NodeJs
基于不同的技术实现了各自的 Evevt Loop
.
JS
语言就是采用这种机制,来解决单线程运行带来的一些问题。
堆、栈、队列 (补充点)
堆(Heap)
堆 是一种数据结构,是利用完全二叉树维护的一组数据,堆分为两种,一种为最大堆,一种为最小堆,将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。 堆是线性数据结构,相当于一维数组,有唯一后继。
栈(Stack)
栈 在计算机科学中是限定仅在表尾进行插入或删除操作的线性表。 栈是一种数据结构,它按照后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据。 先进后出
队列(Queue)
先进先出
Event Loop
在 JavaScript
中,任务被分为两种,一种宏任务(MacroTask
)也叫 Task
,一种叫微任务(MicroTask
)。
MacroTask(宏任务)
一些异步任务的回调会依次进入 MacroTask queue
,等待后续被调用,这些异步任务包括:
script
全部代码- setTimeout
- setInterval
- setImmediate (浏览器暂时不支持,只有IE10支持,具体可见MDN)
- I/O
- UI Rendering
MicroTask(微任务)
- Process.nextTick(Node独有)
- Promise
- Object.observe(废弃)
- MutationObserver 具体查看
浏览器中的 Event Loop
Javascript
有一个 main thread
主线程和 call-stack
调用栈(执行栈),所有的任务都会被放到调用栈等待主线程执行。
JS 代码的具体执行流程
通过一张图帮助理解
- 执行全局Script同步代码,这些同步代码有一些是同步语句,有一些是异步语句(比如setTimeout等);
- 全局Script代码执行完毕后,调用栈Stack会清空;
- 从微队列microtask queue中取出位于队首的回调任务,放入调用栈Stack中执行,执行完后microtask queue长度减1;
- 继续取出位于队首的任务,放入调用栈Stack中执行,以此类推,直到直到把microtask queue中的所有任务都执行完毕。注意,如果在执行microtask的过程中,又产生了microtask,那么会加入到队列的末尾,也会在这个周期被调用执行;
- microtask queue中的所有任务都执行完毕,此时microtask queue为空队列,调用栈Stack也为空;
- 取出宏队列macrotask queue中位于队首的任务,放入Stack中执行;
- 执行完毕后,调用栈Stack为空;
- 重复第3-7个步骤;
- 重复第3-7个步骤;
- ......
可以看到,这就是浏览器的事件循环Event Loop
这里归纳3个重点:
- 宏队列 macrotask 一次只从队列中取一个任务执行,执行完后就去执行微任务队列中的任务;
- 微任务队列中所有的任务都会被依次取出来执行,直到 microtask queue 为空;
- 图中没有画UI rendering的节点,因为这个是由浏览器自行判断决定的,但是只要执行 UI rendering,它的节点是在执行完所有的 microtask 之后,下一个 macrotask 之前,紧跟着执行UI render。
案例1:
console.log(1);
setTimeout(() => {
console.log(2);
Promise.resolve().then(() => {
console.log(3)
});
});
new Promise((resolve, reject) => {
console.log(4)
resolve(5)
}).then((data) => {
console.log(data);
})
setTimeout(() => {
console.log(6);
})
console.log(7);
// 1, 4, 7, 5, 2, 3, 6
分析:
- 主线程: 1,4,7 (new Promise() 立即执行)
- 微任务:5.
- 宏任务:2.
- 微任务:3.
- 宏任务:6.
案例2:
console.log('script start')
async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()
setTimeout(function() {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function() {
console.log('promise1')
})
.then(function() {
console.log('promise2')
})
console.log('script end')
// script start, async2 end, Promise, script end, async1 end, promise1, promise2, setTimeout
async/await
在底层转换成了 promise
和 then
回调函数。其实就是 Promise
的语法糖。
每次我们使用 await
, 解释器都创建一个 promise
对象,然后把剩下的 async
函数中的操作放到 then
回调函数中。