这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战
调用栈(执行上下文栈)
在了解事件循环之前很有必要知道调用栈的概念。首先我们栈是先进后出的执行任务。
而调用栈也是按照这个顺序执行,同时当我们运行全局代码或是每个函数的时候都会创建相对应的执行上下文。当我们执行代码的时候,首先会创建一个全局的执行上下文,之后遇到调用函数就会把调用函数的执行上下文入栈,等执行完后进行出栈,然后继续往后执行直到整个调用栈为空。文字描述过于抽象,以代码举例。
<script type="text/javascript">
function first() {
console.log('Inside first function');
second();
console.log('Again inside first function');
}
function second() {
console.log('Inside second function');
}
console.log('Inside Global Execution Context');
first();
</script>
看这js 脚本,大家应该很容易就能看出输出顺序,但是在每个函数是如何进栈出栈的呢,我列出了一个流程图,可以清晰地看出调用栈的活动
Eventloop
因为js是单线程的,如果我们都是同步执行任务,只有一个任务出栈后才能入栈执行另一个任务,当达到一定体积的时候势必会产生阻塞。所以就引入了异步的执行任务。
- 同步任务和异步任务进入不同的流程,同步任务进入主线程,异步的进入event table 并注册函数
- 当指定的事情完成时,event table 会将这个函数移入 event queue
- 主线程内的任务执行完毕为空,会去 event queue 读取对应的函数,进入主线程执行。
- 上述过程不断重复,也就是event loop 的流程
而异步任务分为宏任务和微任务。
宏任务
- 整体script
- Settimeout
- Setinterval
- setimmediate
微任务
- Promise
- Process.nexttick
- MutationObserver
当主线程中的同步任务执行完毕后会去检查异步任务的event queue,然后拿出微任务队列的任务先执行,如果所有微任务都执行完,并且主线程为空去拿宏任务队列的任务执行,宏任务永远是最后执行的
举个例子
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')
输出:
总体来说就是,如果按照脚本顺序,如果遇到同步任务或微任务按照脚本顺序执行,之后遇到是微任务和宏任务都先存入队列,先把微任务执行完后执行宏任务。