先看看大佬的文章👀
涉及概念
- 执行上下文(Execution context)
- 函数调用栈(call stack)
- 任务队列(task queue)
- Promise
Javascript代码执行过程中,执行顺序依靠 函数调用栈、**任务队列 **来维护的
JavaScript的一大特点就是单线程,而这个线程中拥有唯一的一个事件循环
- 一个线程中,事件循环时唯一的,但是任务队列可以有多个
- 任务队列分为:
macro-task
(宏任务)、micro-task
(微任务) macro-task
:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。micro-task
: process.nextTick, Promise, Object.observe(已废弃), MutationObserver(html5新特性)- setTimeout/Promise 等我们称之为任务源/任务分发器。而进入任务队列的是他们指定的具体执行任务。
- 来自不同任务源的任务会进入到不同的任务队列。其中**
setTimeout
与setInterval
**是同源的 - 事件循环的顺序,决定了JavaScript代码的执行顺序。
- 执行
macro-task
中的 script(整体代码),开始第一次循环,上下文进入函数调用栈,执行可执行代码,将遇到的任务入对应的队列,直到调用栈清空 - 执行
micro-task
中的任务,通过调用栈执行,遇到任务入对应的队列,清空由执行宏任务产生的微任务,一次循环完成 - 执行
macro-task
中的任务,开始第二次循环,通过调用栈执行,遇到任务入对应的队列 micro -> macro -> micro -> macro ....
- 执行
- 其中每一个任务的执行,无论是
macro-task
还是micro-task
,都是借助函数调用栈来完成
注意结束的时间点⏱️
事件循环总结:
先执行宏任务,在执行宏任务的过程中会产生微任务以及宏任务,在结束当前次宏任务后,去执行清空产生的微任务,当然宏任务执行的过程中也会产生宏任务,排在后续队列中执行上次循环产生的宏任务,开启下一次循环- 先前对于有所误解,不好意思😁
- 事件循环,主要涉及三个模块:函数调用栈、任务队列、异步模块
- 每次从宏任务队列开始,拿出一个宏任务进入函数调用栈,进行执行。
- 函数调用栈执行完毕,去清空此次产生的微任务队列中的任务。
- 再回到宏任务队列取一个任务去函数调用栈中执行,依此反复。
- 注意,例如再函数调用栈中执行的过程中,产生的异步任务,如 setTimeout,是先进入 异步模块 (WebAPI),然后在合适的时机在进入到响应的宏任务队列。
栗子🌰
setTimeout(function() {
console.log(1);
})
new Promise(function(resolve) {
console.log(2);
for(var i = 0; i < 1000; i++) {
i == 99 && resolve();
}
console.log(3);
}).then(function() {
console.log(4);
})
console.log(5);
最终输出结果:2 3 5 4 1
-
首先,
macro-task
中只有script
,循环开始 -
执行可执行上下文,以及将其他宏任务、微任务入队列
setTimeout() -> macro-task
new promise().then -> micro-task
构造函数部分是在new的时候执行,不进入队列,直接入函数调用栈执行,所以此时依次输出 2、3
-
继续执行到最后
console.log(5)
,此时输出 5 -
至此宏任务第一个队列执行完毕,去清空,此次产生的微任务队列,及
promise().then
,此时输出 4 -
清空完毕,继续回到宏任务队列,再次循环,及执行
setTimeout
,此时输出 1 -
至此清空,结束循环
over~🎉