事件循环Eventloop的那些点点滴滴
以下是博主最近看一些关于事件循环的博客&&刷题得到的一些经验(还在听dang课真的太不容易了
What is 栈、堆和事件循环以及任务队列
- 栈:在JavaScript中,栈是一个存储普通数据类型的地方,也存放一些简单的数据段。会自动分配内存空间,并且会自动释放,还有一个很重要的作用就是提供代码执行环境
- 堆:JavaScript中堆是存放引用类型数据的地方,动态分配内存,也不会自动释放,实际存放的是指向对象的指针,简单的理解就是一个代码块
可以看到栈中存放的是,指向对象的内容的地址,而堆中存放的是对象实际的数据。
3. ****
- 事件循环:事件轮询是指主线程重复从任务队列中取任务、执行任务的过程。
- 任务队列:任务队列是一个先进先出的队列,它里面存放着各种任务回调。
事件循环详解
- 事件循环的执行策略
- 一开始的整整个
JavaScript
是最开始的一个执行代码,而事件队列会不断循环的去取tasks
队列的中最老的一个task
(可以理解为宏任务)推入栈中执行,并在当次循环里依次执行并清空microtask
队列(微任务)里的任务。 - 执行完
microtask
队列里的任务,有可能会渲染更新。(浏览器很聪明,在一帧以内的多次dom变动浏览器不会立即响应,而是会积攒变动以最高60HZ(大约16.7ms每帧)的频率更新视图)
- 宏任务与微任务的执行顺序
在任务队列中注册的异步回调又会分为两类,也就是宏任务和微任务两种任务队列
宏任务有哪些?
- script(整体代码)
- setTimeout/setInterval(定时器)
- setImmediate(Node环境)
- UI 渲染
- requestAnimationFrame
- …
微任务有哪些? - Promise的then()、catch()、finally()里面的回调
- process.nextTick(Node 环境)
那么执行顺序又是如何?下面是博主自己的理解:
代码从开始执行调用一个全局执行栈,script标签作为宏任务执行
在执行的过程中,同步代码将顺序执行,对于产生的异步回调将他们分为宏任务和微任务,并且分别存放到宏任务队列中和微任务队列中,等待第一个宏任务(script)执行结束后。开始以下步骤:
- 查看微任务队列中是否有宏任务执行过程中产生的微任务
- 如果有,那么就把微任务队列清空,这些微任务将依次执行
- 在清空这些微任务的过程中如果也产生了其他的微任务,也放到微任务队列中,这次执行的过程将一并情况
- 如果没有微任务,那么就看看宏任务队列,有的话则清空队列,没有的话,这一轮就结束
- 执行的过程中,如果产生了微任务,那么就存放到微任务队列
- 在执行完宏任务之后,需要把微任务队列清空。
所以是宏任务优先,在宏任务执行完毕之后才会来一次性清空任务队列中的所有微任务。
亿写小·细节
promise.then().then()
在注册异步回调的时候,第二个then
的回调是依赖第一个then中回调的结果的,如果执行没有异常才会在该异步任务执行完毕之后注册状态对应的回调。在具体的代码执行过程中,是会放到当前所在微任务队列的最后一个,因为第二个then
是需要等待一个then
的执行结果而确定是否执行该回调,而此时在它前面的微任务都已经存在于微任务队列中了。- 关于async/await。await的真实意思是 async wait(异步等待的意思)await表达式相当于调用后面返回promise的then方法,异步(等待)获取其返回值。即
await<==>promise.then
。一般await
表达式后面的代码都可以直接放到微任务里面去,但是如果await表达式没有获取到异步的结果的话,那后面的代码压根也就不会注册异步回调,也就压根不会执行了。
代码实操
- 写出下列程序的输出结果,要求顺序全部正确。
console.log('1');
setTimeout(() => { //宏2
console.log('2');
Promise.resolve().then(() => { //微2-1
console.log('3');
})
new Promise((resolve) => {
console.log('4');
resolve();
}).then(() => { //微2-2
console.log('5')
})
})
Promise.reject().then(() => { //微1
console.log('13');
}, () => {
console.log('12');
})
new Promise((resolve) => {
console.log('7');
resolve();
}).then(() => { //微2
console.log('8')
})
setTimeout(() => { //宏3
console.log('9');
Promise.resolve().then(() => { //微3-1
console.log('10');
})
new Promise((resolve) => {
console.log('11');
resolve();
}).then(() => { //微3-2
console.log('12')
})
})
答案 1 -> 7 -> 12 -> 8 -> 2 -> 4 -> 3 -> 5 -> 9 -> 11 -> 10 -> 12
个人理解
首先执行全局的JavaScript
代码,输出1,碰到第一个setTimeout
,这是一个宏任务记作宏1,直接跳过执行,然后碰到后面的Promise
对象,这是一个微任务,因为直接给出了reject
的状态,就直接执行then
这个微任务。然后碰到后面的生成一个Promise
对象,输出7,然后给定状态 这是一个微任务,跳过。然后又碰到一个setTimeout
,这是第三个宏任务,跳过。至此,第一个宏任务的同步代码就执行完了。然后清空相应的微任务队列。第一个微任务根据reject
状态可知,输出12,然后输出8。至此微任务队列清空。于是我们检查宏任务队列,发现有,那么去执行第二个宏任务。首先输出2,然后碰到一个给定了状态的Promise
对象,这是一个微任务,下面是生成一个Promise
对象实例,输出4,接下来的then
是一个微任务。至此第二个宏任务执行完毕,然后清空微任务队列,依次输出3和5。然后执行第三个宏任务。首先输出9,然后是一个给定了状态的Promise
对象,这是一个微任务,然后是生成Promise
实例,输出11,然后是一个then
方法,这是一个微任务。至此最后一个宏任务也执行完毕,开始清空微任务队列,然后就是一次输出10和12。
- 这是一个层次还比较分明的一个程序了,接下来咱们看一个比较难的。
async function async1() {
console.log('async1 start');
new Promise((resolve, reject) => {
try {
throw new Error('error1')
} catch(e) {
console.log(e);
}
setTimeout(() => { //宏3
resolve('promise4')
}, 3 * 1000);
})
.then((res) => { //微3-1
console.log(res);
}, err => {
console.log(err);
})
.finally(res => { //微3-2
console.log(res);
})
console.log(await async2());
console.log('async1 end'); //微4-2
}
function async2() {
console.log('async2');
return new Promise((resolve) => { //宏4
setTimeout(() => {
resolve(2) //微4-1
}, 1 * 3000);
})
}
console.log('script start');
setTimeout(() => { //宏2
console.log('setTimeout');
}, 0)
async1();
new Promise((resolve) => {
console.log('promise1');
resolve();
})
.then(() => {
console.log('promise2'); //微1-1
return new Promise((resolve) => {
resolve()
})
.then(() => { //微1-2
console.log('then 1-1')
})
})
.then(() => { //微1-3
console.log('promise3');
})
console.log('script end');
咱们就不直接给答案,一步一步来做(主要是我也要慢慢做哈哈哈)
老规矩执行第一个宏任务,输script start,碰到一个定时器,就是第二个宏任务了。重点来了,执行了函数async1
,这里要注意,这个函数使用了async
,也就是会等到await
的执行,所以首先输出async1 start,然后碰到一个Promise
实例的形成,这里抛出异常,于是输出error1,接下来是一个定时器,那就是第二个宏任务。后面是then
方法了,是微任务,但是这个微任务必须等其Promise
对象确定状态之后才可以执行,但是这个状态确定却在定时器里面,所以这是宏3里的一个微任务。然后执行函数async2,会输出async2,然后返回一个Promise
对象,但是里面是一个定时器,所以是第四个宏任务。然后await
表达式后面的是需要放到微任务中,但是是在结束宏任务4之后的事,所以那里是宏任务4结束后应该处理的微任务。但是是第一个嘛?不是!因为我们说过了,await
相当于promise.then
,所以第四个宏任务中的resolve(2)
,是会被await
相当于执行了一个then
,方法,所以这里是宏任务4后的第一个微任务,而await
表达式后面的代码,是第二个。回到第一个宏任务,接下来是实例化一个Promise
对象,输出promise1,后面的then
方法就是一个微任务了,但是这里面还返回一个promise
对象,并且给定了状态,则是接下来的微任务,然后我们碰到了最外围的promise
对象的第二个then
,由于这里第一个then
里面是已经确定了状态,所以这个then
放到当前微任务队列的最后面。然后是输出script end。然后依次清空所有宏任务1后面的微任务队列,一次输出promise2,then1-1和promise3。至此,第一个宏任务执行完毕,执行第二个宏任务,输出setTimeout。然后并没有需要清空的微任务,所以直接进行第三个宏任务。宏任务3本身没有没后任何的输出,但是给出了一个promise
对象的状态。于是后面的then
方法就可以输出promise4,后面还一个final
方法,这个方法是无论怎么样都需要执行的,由于没有任何的传参,那就是直接输出undefined。至此第三个宏任务也结束了,第四个宏任务完成后是返回了一个promise
对象。所以在async1
函数的底部,await
会直接执行这个promise
对象,同时会执行后面的代码,也就是顺序输出2和async1 end。
然后整个程序就全部运行完了!!
整体程序的运行结果如下:
script start -> async1 start -> error1 -> async2 -> promise1 -> script end
微1-1: promise2 -> 微1-2: then 1-1 -> 微1-3: promise3
宏2:setTimeout
微3-1: promise4 -> 微3-2: undefined
微4-1: 2 -> 微4-2: async1 end
以上就是博主对于事件循环的一些理解,虽然理解的并不好,所以呢,还是要继续学习,还是要去看看文档啊,前端🦈我!回去追剧,下次见!
听雷2真的讨好看了