前言
在写这篇文章之前,我看了很多写的不错的文章,但是每篇文章都有那么几个关键的点,很多篇文章凑在一起综合来看,才可以对这些概念有较为深入的理解。所以,我就想要写这么一篇文章,结合自己的理解以及示例代码,用最通俗的文字表达出来。
希望大家可以通过这篇文章,学习到Event loop的运行原理。如果在文中出现有错误的地方,欢迎大家留言一起探讨。
正文
在开始之前,先理解一下三个概念:堆、栈、队列
堆(Heap)
堆是一种数据结构,是利用完全二叉树维护的一组数据,堆分为两种,一种为最大堆,一种为最小堆。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆是线性数据结构,相当于一维数组,有唯一后继。
栈(Stack)
栈在计算机科学中是限定仅在表尾进行插入或删除操作的线性表。栈是一种数据结构,它按照后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据。
栈是只能在某一端插入和删除的特殊线性表。
队列(Queue)
队列特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。
进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。
队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)
什么是Event loop?
event loop是一个执行模型,在不同的地方有不同的实现。浏览器和NodeJS基于不同的技术实现了各自的Event Loop。
在JavaScript中,任务被分为两种,一种宏任务(MacroTask)也叫Task,一种叫微任务(MicroTask)。
MacroTask(宏任务)
一些异步任务的回调会依次进入macro task queue(宏任务队列),等待后续被调用,这些异步任务包括:
MicroTask(微任务)
另一些异步任务的回调会依次进入micro task queue(微任务队列),等待后续被调用,这些异步任务包括:
浏览器的Event loop
Javascript 有一个 main thread 主线程和 call-stack 调用栈(执行栈),所有的任务都会被放到调用栈等待主线程执行。
JS调用栈
JS调用栈采用的是后进先出的规则,当函数执行的时候,会被添加到栈的顶部,当执行栈执行完成后,就会从栈顶移出,直到栈内被清空。
同步任务和异步任务
Javascript单线程任务被分为同步任务和异步任务,同步任务会在调用栈中按照顺序等待主线程依次执行,异步任务会在异步任务有了结果后,将注册的回调函数放入任务队列中等待主线程空闲的时候(调用栈被清空),被读取到栈内等待主线程的执行。
任务队列Task Queue,即队列,是一种先进先出的一种数据结构。
执行进入microtask检查点时,会执行以下步骤:
可能上面说的比较抽象,那下面我们用一个例子理解一下
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
第一次执行
执行同步代码将宏任务(Tasks)和微任务(Microtasks)划分到各自队列中
打印log:script start
将setTimeout放入宏任务(Tasks)队列
将Promise.then放入到微任务(Microtask)队列
打印log:script end
打印结果:script start、script end
第二次执行
执行宏任务时检测到微任务(Microtasks)队列中不为空,去执行微任务
执行Promise1,执行Promise1完成后
调用Promise2.then
将Promise2.then放入微任务(Microtasks)队列中
再执行Promise2.then
打印结果:promise1、promise2
第三次执行
当微任务(Microtasks)队列中为空时,执行宏任务(Tasks)
执行 setTimeout callback
打印结果:setTimeout
最后一次执行
清空Tasks队列和JS stack
下面再来一个比较复杂的例子
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')
解析之前,这里需要先理解async/await。 async/await 在底层转换成了 promise 和 then 回调函数。 也就是说,这是 promise 的语法糖。 每次我们使用 await, 解释器都创建一个 promise 对象,然后把剩下的 async 函数中的操作放到 then 回调函数中。 async/await 的实现,离不开 Promise。从字面意思来理解,async 是“异步”的简写,而 await 是 async wait 的简写可以认为是等待异步方法执行完成。
现在对async/await有来初步认识,现在下面开始解析代码