总结事件轮询机制,以及宏任务队列与微任务队列

2024-08-22 03:58

本文主要是介绍总结事件轮询机制,以及宏任务队列与微任务队列,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

  • 1. 事件轮询(Event Loop)
    • js实现异步的具体解决方案
    • 什么叫轮询?
  • 2. 宏任务和微任务
    • 概念
    • 宏任务
    • 微任务
    • 例题
      • EXP1: 在主线程上添加宏任务与微任务
      • EXP2: 在微任务中创建微任务
      • EXP3: 宏任务中创建微任务
      • EXP4:微任务队列中创建的宏任务
    • 总结

这篇博文仅为个人理解,文章内提供一些更加权威的参考,如有片面及错误,欢迎指正

1. 事件轮询(Event Loop)

什么是 Event Loop? - 阮一峰

事件轮询(Event Loop) - 《你不懂JS:异步与性能》

【推荐】详解JavaScript中的Event Loop(事件循环)机制

Javascript的宿主环境中共通的一个“线程”(一个“不那么微妙”的异步玩笑,不管怎样)是,他们都有一种机制:在每次调用JS引擎时,可以随着时间的推移执行你的程序的多个代码块儿,这称为“事件轮询(Event Loop)”。

换句话说,JS引擎对 时间 没有天生的感觉,反而是一个任意JS代码段的按需执行环境。是它周围的环境在不停地安排“事件”(JS代码的执行)。

js实现异步的具体解决方案

  • 同步代码直接执行
  • 异步函数到了指定时间再放到异步队列
  • 同步执行完毕,异步队列轮询执行。

什么叫轮询?

精简版:当第一个异步函数执行完之后,再到异步队列监视。一直不断循环往复,所以叫事件轮询。

详细版:js引擎遇到一个异步事件后并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当一个异步事件返回结果后,js会将这个事件加入与当前执行栈不同的另一个队列,我们称之为事件队列。被放入事件队列不会立刻执行其回调,而是等待当前执行栈中的所有任务都执行完毕, 主线程处于闲置状态时,主线程会去查找事件队列是否有任务。如果有,那么主线程会从中取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,然后执行其中的同步代码…,如此反复,这样就形成了一个无限的循环。这就是这个过程被称为“事件循环(Event Loop)”的原因。

事实上,事件轮询与宏任务和微任务密切相关。

2. 宏任务和微任务

概念

微任务、宏任务与Event-Loop

在一个事件循环中,异步事件返回结果后会被放到一个任务队列中。然而,根据这个异步事件的类型,这个事件实际上会被对应的宏任务队列或者微任务队列中去。并且在当前执行栈为空的时候,主线程会 查看微任务队列是否有事件存在。如果不存在,那么再去宏任务队列中取出一个事件并把对应的回到加入当前执行栈;如果存在,则会依次执行队列中事件对应的回调,直到微任务队列为空,然后去宏任务队列中取出最前面的一个事件,把对应的回调加入当前执行栈…如此反复,进入循环。

我们只需记住当当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。

在当前的微任务没有执行完成时,是不会执行下一个宏任务的。
所以就有了那个经常在面试题、各种博客中的代码片段:

setTimeout(_ => console.log(4))new Promise(resolve => {resolve()console.log(1)
}).then(_ => {console.log(3)
})console.log(2)

setTimeout就是作为宏任务来存在的,而Promise.then则是具有代表性的微任务,上述代码的执行顺序就是按照序号来输出的。
所有会进入的异步都是指的事件回调中的那部分代码
也就是说new Promise在实例化的过程中所执行的代码都是同步进行的,而then中注册的回调才是异步执行的。
在同步代码执行完成后才回去检查是否有异步任务完成,并执行对应的回调,而微任务又会在宏任务之前执行。
所以就得到了上述的输出结论1、2、3、4。

+部分表示同步执行的代码

+setTimeout(_ => {
-  console.log(4)
+})+new Promise(resolve => {
+  resolve()
+  console.log(1)
+}).then(_ => {
-  console.log(3)
+})+console.log(2)

本来setTimeout已经先设置了定时器(相当于取号),然后在当前进程中又添加了一些Promise的处理(临时添加业务)。

所以进阶的,即便我们继续在Promise中实例化Promise,其输出依然会早于setTimeout的宏任务:如EXP2

宏任务

分类:

#浏览器Node
I/O
setTimeout
setInterval
setImmediate
requestAnimationFrame

特性:

  1. 宏任务所处的队列就是宏任务队列

  2. 第一个宏任务队列中只有一个任务:执行主线程上的JS代码;如果遇到上方表格中的异步任务,会创建出一个新的宏任务队列,存放这些异步函数执行完成后的回调函数。

  3. 宏任务队列可以有多个

  4. 宏任务中可以创建微任务,且如果该微任务立即被加入执行栈的话,会打断当前宏任务的执行。(EXP3)

  5. 当一个宏任务队列中的任务全部执行完后,会查看是否有微任务队列,如果有就会优先执行微任务队列中的所有任务,如果没有就查看是否有宏任务队列

微任务

分类:

#浏览器Node
process.nextTick
MutationObserver
Promise.then catch finally

特性:

  1. 微任务所处的队列就是微任务队列

  2. 在上一个宏任务队列执行完毕后,如果有微任务队列就会执行微任务队列中的所有任务

  3. new promise((resolve)=>{ 这里的函数在当前队列直接执行 }).then( 这里的函数放在微任务队列中执行 )

  4. 微任务队列上创建的微任务,仍会阻碍后方将要执行的宏任务队列 (EXP2)

  5. 由微任务创建的宏任务,会被丢在异步宏任务队列中执行 (EXP4)

例题

EXP1: 在主线程上添加宏任务与微任务

执行顺序:主线程 => 主线程上创建的微任务 => 主线程上创建的宏任务

console.log('-------start--------');setTimeout(() => {console.log('setTimeout');  // 将回调代码放入另一个宏任务队列
}, 0);new Promise((resolve, reject) => {for (let i = 0; i < 5; i++) {console.log(i);}resolve()
}).then(()=>{console.log('Promise实例成功回调执行'); // 将回调代码放入微任务队列
})console.log('-------end--------');

结果:

-------start--------
0
1
2
3
4
-------end--------
Promise实例成功回调执行
setTimeout

由EXP1,我们可以看出,当JS执行完主线程上的代码,会去检查在主线程上创建的微任务队列,执行完微任务队列之后才会执行宏任务队列上的代码

EXP2: 在微任务中创建微任务

执行顺序:主线程 => 主线程上创建的微任务1 => 微任务1上创建的微任务2 => 主线程上创建的宏任务

setTimeout(_ => console.log(4))new Promise(resolve => {resolve()console.log(1)
}).then(_ => {console.log(3)Promise.resolve().then(_ => {console.log('before timeout')}).then(_ => {Promise.resolve().then(_ => {console.log('also before timeout')})})
})console.log(2)

结果:

1
2
3
before timeout
also before timeout
4

由EXP1,我们可以看出,在微任务队列执行时创建的微任务,还是会排在主线程上创建出的宏任务之前执行

EXP3: 宏任务中创建微任务

宏任务队列中创建的微任务,会打断当前宏任务队列的执行。

执行顺序:主线程(宏任务队列 1)=> 宏任务队列 2.1 => 微任务队列 1(打断宏任务队列 2)=>宏任务队列 2.2 => 宏任务队列 3

// 宏任务队列 1
setTimeout(() => {// 宏任务队列 2.1console.log('timer_1');setTimeout(() => {// 宏任务队列 3console.log('timer_3')}, 0)new Promise(resolve => {resolve()console.log('new promise')}).then(() => {// 微任务队列 1console.log('promise then')})
}, 0)setTimeout(() => {// 宏任务队列 2.2console.log('timer_2')
}, 0)console.log('========== Sync queue ==========')

结果:

========== Sync queue ==========
timer_1
new promise
promise then
timer_2
timer_3

EXP4:微任务队列中创建的宏任务

执行顺序:主线程 => 主线程上创建的微任务 => 主线程上创建的宏任务 => 微任务中创建的宏任务

异步宏任务队列只有一个,当在微任务中创建一个宏任务之后,他会被追加到异步宏任务队列上(跟主线程创建的异步宏任务队列是同一个队列)

// 宏任务1
new Promise((resolve) => {console.log('new Promise(macro task 1)');resolve();
}).then(() => {// 微任务1console.log('micro task 1');setTimeout(() => {// 宏任务3console.log('macro task 3');}, 0)
})setTimeout(() => {// 宏任务2console.log('macro task 2');
}, 1000)console.log('========== Sync queue(macro task 1) ==========');

结果:

========== Sync queue(macro task 1) ==========
micro task 1
macro task 3
macro task 2

总结

微任务队列优先于宏任务队列执行,微任务队列上创建的宏任务会被后添加到当前宏任务队列的尾端,微任务队列中创建的微任务会被添加到微任务队列的尾端。只要微任务队列中还有任务,宏任务队列就只会等待微任务队列执行完毕后再执行。

最后上一张几乎涵盖基本情况的例图和例子

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E9JbttZi-1622687528232)(https://ws1.sinaimg.cn/large/a71efaafly1g232hxfbhrj21350h9406.jpg)]

console.log('======== main task start ========');
new Promise(resolve => {console.log('create micro task 1');resolve();
}).then(() => {console.log('micro task 1 callback');setTimeout(() => {console.log('macro task 3 callback');}, 0);
})console.log('create macro task 2');
setTimeout(() => {console.log('macro task 2 callback');new Promise(resolve => {console.log('create micro task 3');resolve();}).then(() => {console.log('micro task 3 callback');})console.log('create macro task 4');setTimeout(() => {console.log('macro task 4 callback');}, 0);
}, 0);new Promise(resolve => {console.log('create micro task 2');resolve();
}).then(() => {console.log('micro task 2 callback');
})console.log('======== main task end ========');

结果:

======== main task start ========
create micro task 1
create macro task 2
create micro task 2
======== main task end ========
micro task 1 callback
micro task 2 callback
macro task 2 callback
create micro task 3
create macro task 4
micro task 3 callback
macro task 3 callback
macro task 4 callback

这篇关于总结事件轮询机制,以及宏任务队列与微任务队列的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1095173

相关文章

一文带你理解Python中import机制与importlib的妙用

《一文带你理解Python中import机制与importlib的妙用》在Python编程的世界里,import语句是开发者最常用的工具之一,它就像一把钥匙,打开了通往各种功能和库的大门,下面就跟随小... 目录一、python import机制概述1.1 import语句的基本用法1.2 模块缓存机制1.

Redis主从/哨兵机制原理分析

《Redis主从/哨兵机制原理分析》本文介绍了Redis的主从复制和哨兵机制,主从复制实现了数据的热备份和负载均衡,而哨兵机制可以监控Redis集群,实现自动故障转移,哨兵机制通过监控、下线、选举和故... 目录一、主从复制1.1 什么是主从复制1.2 主从复制的作用1.3 主从复制原理1.3.1 全量复制

Redis延迟队列的实现示例

《Redis延迟队列的实现示例》Redis延迟队列是一种使用Redis实现的消息队列,本文主要介绍了Redis延迟队列的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习... 目录一、什么是 Redis 延迟队列二、实现原理三、Java 代码示例四、注意事项五、使用 Redi

Redis缓存问题与缓存更新机制详解

《Redis缓存问题与缓存更新机制详解》本文主要介绍了缓存问题及其解决方案,包括缓存穿透、缓存击穿、缓存雪崩等问题的成因以及相应的预防和解决方法,同时,还详细探讨了缓存更新机制,包括不同情况下的缓存更... 目录一、缓存问题1.1 缓存穿透1.1.1 问题来源1.1.2 解决方案1.2 缓存击穿1.2.1

Java如何通过反射机制获取数据类对象的属性及方法

《Java如何通过反射机制获取数据类对象的属性及方法》文章介绍了如何使用Java反射机制获取类对象的所有属性及其对应的get、set方法,以及如何通过反射机制实现类对象的实例化,感兴趣的朋友跟随小编一... 目录一、通过反射机制获取类对象的所有属性以及相应的get、set方法1.遍历类对象的所有属性2.获取

Python Invoke自动化任务库的使用

《PythonInvoke自动化任务库的使用》Invoke是一个强大的Python库,用于编写自动化脚本,本文就来介绍一下PythonInvoke自动化任务库的使用,具有一定的参考价值,感兴趣的可以... 目录什么是 Invoke?如何安装 Invoke?Invoke 基础1. 运行测试2. 构建文档3.

MySQL中的锁和MVCC机制解读

《MySQL中的锁和MVCC机制解读》MySQL事务、锁和MVCC机制是确保数据库操作原子性、一致性和隔离性的关键,事务必须遵循ACID原则,锁的类型包括表级锁、行级锁和意向锁,MVCC通过非锁定读和... 目录mysql的锁和MVCC机制事务的概念与ACID特性锁的类型及其工作机制锁的粒度与性能影响多版本

解决Cron定时任务中Pytest脚本无法发送邮件的问题

《解决Cron定时任务中Pytest脚本无法发送邮件的问题》文章探讨解决在Cron定时任务中运行Pytest脚本时邮件发送失败的问题,先优化环境变量,再检查Pytest邮件配置,接着配置文件确保SMT... 目录引言1. 环境变量优化:确保Cron任务可以正确执行解决方案:1.1. 创建一个脚本1.2. 修

Python中实现进度条的多种方法总结

《Python中实现进度条的多种方法总结》在Python编程中,进度条是一个非常有用的功能,它能让用户直观地了解任务的进度,提升用户体验,本文将介绍几种在Python中实现进度条的常用方法,并通过代码... 目录一、简单的打印方式二、使用tqdm库三、使用alive-progress库四、使用progres

Java实现任务管理器性能网络监控数据的方法详解

《Java实现任务管理器性能网络监控数据的方法详解》在现代操作系统中,任务管理器是一个非常重要的工具,用于监控和管理计算机的运行状态,包括CPU使用率、内存占用等,对于开发者和系统管理员来说,了解这些... 目录引言一、背景知识二、准备工作1. Maven依赖2. Gradle依赖三、代码实现四、代码详解五