三个概念:
- 执行栈:js代码执行的一个封闭环境,在执行栈内部,代码都是同步执行的,如果遇到异步任务,则将其推入任务队列;
- 宏任务队列(MacroTask):setTimeout、setInterval、setImmediate或者I/O等异步操作的回调任务存放在该队列;
- 微任务队列(microTask):Promise、Mutation Observer、process.nextTick等异步操作的回调任务存放在该队列。
流程:
代码首先在执行栈中执行,遇到宏任务或者微任务,直接将其推入对应的队列中,后续调用。执行栈代码执行完毕后,栈空,优先清空微任务队列,每一个微任务内部的代码在执行时,又相当于进入了一个新的执行栈,后续过程与之前相同。直到微任务队列为空,此时我们去将宏任务队列中最先入队的任务揪出来,进入一个新的执行栈,该栈为空时,还是会优先清空微任务队列(如果此次执行又推入新的微任务的话)。
这就是事件循环的过程。
清晰解读
首先明确几个概念:
js引擎线程(执行js代码)定时器触发线程(负责在指定时间延迟后将回调函数推入事件队列)异步http请求线程(在请求结束后将回调函数推入事件队列)事件触发线程(维护一个事件队列)
流程:
js引擎线程在执行栈中执行同步代码,如果遇到setTimeout/setInterval,会通知定时触发器线程,而定时器线程在间隔时间后会通知事件触发线程,将回调函数推入事件触发线程维护的事件队列中。当js引擎线程空闲时,会询问事件触发线程,是否有未执行的事件,有则将其推入执行栈。
GUI渲染线程
与js引擎线程互斥,两者只能同时运行一个。浏览器将两者的运行机制搞得很明白。
当js引擎线程空闲时,GUI渲染线程会立即执行,进行页面的渲染更新。
例子:
new Promise(resolve => {
resolve();
}).then(() => {
console.log('then1');
console.log('推1-1入队');
new Promise(resolve => {
resolve();
}).then(() => {
console.log('then1-1');
console.log('推1-2入队');
}).then(() => {
console.log('then1-2')
});
console.log('推2入队');
}).then(() => {
console.log('then2');
console.log('推2-1入队');
new Promise(resolve => {
resolve();
}).then(() => {
console.log('then2-1');
console.log('推2-1-1入队');
new Promise((resolve) => {
resolve();
}).then(() => {
console.log('then2-1-1');
console.log('推2-1-2入队');
}).then(() => {
console.log('then2-1-2')
});
console.log('推2-2入队');
}).then(() => {
console.log('then2-2');
console.log('推2-3入队');
}).then(() => {
console.log('then2-3');
});
console.log('推3入队');
}).then(() => {
console.log('then3');
console.log('推3-1入队');
new Promise(res => {
res();
}).then(() => {
console.log('then3-1');
console.log('推3-2入队');
}).then(() => {
console.log('then3-2');
});
});
大家可以去执行试试看。
bye。