JavaScript的事件队列(Event Queue)---宏任务和微任务
前言
在写代码的时候经常思考一个问题,到底是那个函数先执行,本身JavaScript是一门单线程的语言,意思就是按照顺序执行。但是加入一些setTimeout和promise的函数来又实现了异步操作,常常我会写一个setTimeout(fn,0),他会立即执行吗?
宏任务和微任务
首先我们先来看一段代码:
<script>
console.log("Start"); setTimeout(function(){
console.log("SetTimeout");
},); new Promise(function(resolve,reject){
console.log("Promise");
resolve();
}).then(function(){
console.log("Then");
}); console.log("End");
<script>
这些日志的打印顺序是:
Start
Promise
End
Then
SetTimeout
这是为什么
首先,我们知道JavaScript的一大特点就是单线程,而这个线程中拥有唯一的一个事件循环。
一个线程中,事件循环是唯一的,但是任务队列可以拥有多个。
任务队列又分为macro-task(宏任务)与micro-task(微任务),在最新标准中,它们被分别称为task与jobs。
宏任务
- setTimeout
- setInterval
- I/O
- script代码块
微任务
- nextTick
- callback
- Promise
- process.nextTick
- Object.observe
- MutationObserver
事件循环的顺序,决定js代码的执行顺序。一段代码块就是一个宏任务。进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务。
主线程(宏任务) => 微任务 => 宏任务 => 主线程
下图是简易版的事件循环:
所以在上面的代码中宏任务有script代码块,setTimeout,微任务有Promise
事件循环流程分析如下:
- 整体
script作为第一个宏任务进入主线程,遇到console.log,输出Start。 - 遇到
setTimeout,其回调函数被分发到宏任务Event Queue中。 - 遇到
Promise,new Promise直接执行,输出Promise。then被分发到微任务Event Queue中。 - 遇到console.log,立即执行,输出
End。 - 整体代码
script作为第一个宏任务执行结束,看看有哪些微任务?我们发现了then在微任务Event Queue里面,执行 - ok,第一轮事件循环结束了,我们开始第二轮循环,当然要从宏任务Event Queue开始。我们发现了宏任务Event Queue中
setTimeout对应的回调函数,立即执行。 - 所以代码结束。
提高下难度在来一段较为复杂的代码来检验是否已经基本了解了事件循环的机制
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout1');
}, );
setTimeout(function() {
console.log('setTimeout2');
new Promise(function(resolve) {
resolve();
}).then(function() {
console.log('then1')
})
new Promise(function(resolve) {
console.log('Promise1');
resolve();
}).then(function() {
console.log('then2')
})
},)
async1();
new Promise(function(resolve) {
console.log('promise2');
resolve();
}).then(function() {
console.log('then3');
});
console.log('script end');
第一轮事件循环流程分析如下:
整体script作为第一个宏任务进入主线程,async1(),和async12()函数申明,但并没有执行,遇到
console.log输出script start。继续向下执行,遇到
setTimeout,把它的回调函数放入宏任务Event Queue。(ps:暂且叫他setTimeout1)宏任务 微任务 setTimeout1 继续向下执行,又遇到一个
setTimeout,继续将他放入宏任务Event Queue。(ps:暂且叫他setTimeout2)宏任务 微任务 setTimeout1 setTimeout2 遇到执行async1(), 进入
async的执行上下文之后,遇到console.log输出async1 start然后遇到await async2(),由于
()的优先级高,所有立即执行async2(),进入async2()的执行上下文。看到
console.log输出async2,之后没有返回值,结束函数,返回undefined,返回async1的执行上下文的await undefined,由于async函数使用await后得语句会被放入一个回调函数中,所以把下面的放入微任务Event Queue中。宏任务 微任务 setTimeout1 async1 => awati 后面的语句 setTimeout2 结束
async1()遇到Promise,Promise本身是同步的立即执行函数 new Promise直接执行,输出Promise2。then后面的函数被分发到微任务Event Queue中宏任务 微任务 setTimeout1 async1 => awati 后面的语句 setTimeout2 new Promise() => 后的then 执行完
Promise(),遇到console.log,输出script end,这里一个宏任务代码块执行完毕。在主线程执行的过程中,事件触发线程一直在监听着异步事件, 当主线程空闲下来后,若微任务队列中有任务未执行,执行的事件队列(Event Queue)中有微任务,遇到
new Promise()后面的回调函数,执行代码,输出then3。看到
async1中await后面的回调函数,执行代码,输出async1 end(注意:如果俩个微任务的优先级相同那么任务队列自上而下执行,但是promise的优先级高于async,所以先执行promise后面的回调函数)自此,第一轮事件循环正式结束,这一轮的结果是输出:
script start => async1 start => async2 => promise2 => script end => then3 => async1 end
宏任务 微任务 setTimeout1 setTimeout2 那么第二轮时间循环从setTimeout宏任务开始:
setTimeout和setInterval的运行机制是,将指定的代码移出本次执行,等到下一轮Event Loop时,再检查是否到了指定时间。如果到了,就执行对应的代码;如果不到,就等到再下一轮Event Loop时重新判断。因为setTimeout1有200ms的延时,并没到达指定时间,所以先执行setTimeout2这个宏任务
进入到setTimeout2,遇到
console.log首先输出setTimeout2;遇到
Promise,Promise本身是同步的立即执行函数new Promise直接执行。then后面的函数被分发到微任务Event Queue中宏任务 微任务 setTimeout1 new Promise() => 后的then1 再次遇到
Promise,Promise本身是同步的立即执行函数new Promise直接执行输出promise1。then后面的函数被分发到微任务Event Queue中宏任务 微任务 setTimeout1 new Promise() => 后的then1 空 new Promise() => 后的then2 主线程执行执行空闲,开始执行微任务队列中依次输出
then1和then2。第二轮事件循环正式结束。第二轮依次输出
promise1 => then1 => then2
现在任务队列中只有个延时200ms的setTimeout1,在到达200ms后执行setTimeout的回调函数输出
setTimeout1时间循环结束
整段代码,完整的输出为
script start => async1 start => async2 => promise2 => script end => then3 => async1 end => promise1 => then1 => then2 => setTimeout1
总结
- 在执行栈中执行一个宏任务。
- 在执行过程中遇到微任务和宏任务,分别添加到微任务队列和宏任务队列中去。
- 当前宏任务执行完毕,立即执行微任务队列中的任务(微任务存在优先级,优先级高的先执行(promise的优先级高于async))。
- 当前微任务队列中的任务执行完毕,检查渲染,GUI线程接管渲染。
- 继续执行下一个宏任务从事件队列中取。
所以在我们写下setTimeout(fn,0)的时候他并不是在当时立即执行,是从下一个Event loop开始执行,即是等当前所有脚本执行完再运行,就是"尽可能早"。
JavaScript的事件队列(Event Queue)---宏任务和微任务的更多相关文章
- 从几道题目带你深入理解Event Loop_宏队列_微队列
目录 深入探究JavaScript的Event Loop Event Loop的结构 回调队列(callbacks queue)的分类 Event Loop的执行顺序 通过题目来深入 深入探究Java ...
- JS中的执行机制(setTimeout、setInterval、promise、宏任务、微任务)
1.执行机制 JS 是单线程的,处理 JS 任务(程序)只能一个一个顺序执行,所以 JS 中就把任务分为了同步任务和异步任务.同步的进入主线程先执行,异步的进入Event Table并注册函数,当指定 ...
- 详解JavaScript中的Event Loop(事件循环)机制
前言 我们都知道,javascript从诞生之日起就是一门单线程的非阻塞的脚本语言.这是由其最初的用途来决定的:与浏览器交互. 单线程意味着,javascript代码在执行的任何时候,都只有一个主线程 ...
- Event Loop、 宏任务和微任务
本文将介绍我自己对JS Event Loop 和 宏任务.微任务的理解. 二话不说先上图: 接下来将会针对此图讲解什么是Event Loop 什么事宏任务和微任务(其实聪明的你们通过图大体也能了解的是 ...
- javaScript的执行机制-同步任务-异步任务-微任务-宏任务
一.概念理解 1.关于javascript javascript是一门单线程语言,在最新的HTML5中提出了Web-Worker,但javascript是单线程这一核心仍未改变.所以一切javascr ...
- JavaScript同步模式,异步模式及宏任务,微任务队列
首先JavaScript是单线程的语言,也就是说JS执行环境中,负责执行代码的线程只有一个.一次只能执行一个任务,如果有多个任务的话, 就要排队,然后依次执行,优点就是更安全,更简单.缺点就是遇到耗时 ...
- JavaScript事件循环(Event Loop)机制
JavaScript 是单线程单并发语言 什么是单线程 主程序只有一个线程,即同一时间片断内其只能执行单个任务. 为什么选择单线程? JavaScript的主要用途是与用户互动,以及操作DOM.这决定 ...
- 宏任务、微任务与Event Loop
说到宏任务和微任务,我们就不得不提 Event Loop 了 JS的本质是单线: 1. 一般来说,非阻塞性的任务采取同步的方式,直接在主线程的执行栈完成. 2. 一般来说,阻塞性的任务都会采用异步来执 ...
- javascript中的宏任务和微任务(一)
一.宏任务和微任务有哪些 宏任务:setTimeout,setInterval,ajax,dom,宏任务是由浏览器提供的 微任务:promise,async/await,微任务是由es6提供的 二.微 ...
随机推荐
- mysql 表相关操作(1)
查询语句 select * from t_deptselect empno,ename,sal from t_emp select empno, sal * 12 as "inco ...
- 中值滤波器(平滑空间滤波器)基本原理及Python实现
1. 基本原理 一种典型的非线性滤波器就是中值滤波器,它使用像素的一个领域内的灰度的中值来代替该像素的值.中值滤波器通常是处理椒盐噪声的一种有效的手段. 2. 测试结果 图源自skimage 3. 代 ...
- having函数,case when与order by
having:用于筛选分组后的各组数据.聚合函数,和group by一起使用(where不能和聚合函数使用)group by放在order by前使用,放在之后报错SELECT user_id fro ...
- drf三大认证解析
目录 三大认证 认证模块: 权限模块 频率模块 RABC author组件 认证权限六表. Content_type 认证与权限工作原理+自定义认证类 自定义权限类 admin关联自定义用户表 前后台 ...
- 脚本_查看当前系统每个IP的连接数
#!bin/bash#作者:liusingbon#功能:查看当前系统每个IP的连接数netstat -n | awk '/^tcp/ {print $5}'| awk -F: '{print $1}' ...
- [易学易懂系列|rustlang语言|零基础|快速入门|(2)|VSCODE配置]
我们今天来配置下vscode+rust. vscode开发rust很方便.但配置有点坑,我们都认为vscode很简单,很完善. 但这里很多同学也出现不少问题. 我们在这里简单记录下win7下配置的过程 ...
- Lambda学习总结(一)--函数式接口
Lambda 表达式是 JDK 1.8 里面的一个重要更新,这意味着 Java 也开始承认了函数式编程,并且尝试引入其中,我们今天就来了解下它的使用. 一.函数式接口 1.1 概念 函数式接口在 Ja ...
- 一个web应用的诞生(4)
上一章实现了登录的部分功能,之所以说是部分功能,是因为用户名和密码写成固定值肯定是不可以的,一个整体的功能,至少需要注册,登录,密码修改等,这就需要提供一个把这些值存储到数据库的能力. 当前的主流数据 ...
- VxLAN、PAE、Telemetry简介
VxLAN VxLAN协议将 Ethernet帧 封装在UDP内,再加上8个字节的VXLAN header,用来标识不同的二层网络. VxLAN的角度看网络虚拟化:在一套物理网络设备上虚拟出多个二 ...
- ZROI 19.08.12模拟赛
传送门 写在前面:为了保护正睿题目版权,这里不放题面,只写题解. "我发现问题的根源是大家都不会前缀和."--敦爷 A 敦爷spj写错了,差点把蒟蒻swk送走 \(50pts:\) ...