背景

在研究js的异步的实现方式的时候,发现了JavaScript 中的 macrotask 和 microtask 的概念。在查阅了一番资料之后,对其中的执行机制有所了解,下面整理出来,希望可以帮助更多人。

先了解一下js的任务执行机制

首先,javascript是单线程的,所以只能通过异步解决性能问题(否则,如果前面一个任务阻塞了,那么后续的任务都要等待,这种效果是无法接受的)。js在执行代码时存在着两个比较重要的东西:执行栈和任务队列,这两个东西都是用来存储任务的,区别在于:执行栈里面存着的都是同步任务,也就是要按顺序执行的任务;而任务队列中存着的是一些异步任务,这些异步任务一定要等到执行栈清空后才会执行(这句话很重要)。关于任务队列,它还分成两种,一种叫作macrotask queue(姑且这么命名,因为严格来说规范中只有说task,并没有提到macrotask这个概念。这里为了容易区分,可以理解为macrotask=task!=microtask),另一种叫作microtask queue。如果同时考虑node环境和浏览器环境的话,这两种任务分别对应以下api:

microtasks:

process.nextTick

promise

Object.observe

MutationObserver

macrotasks:

setTimeout

setInterval

setImmediate

I/O

UI渲染

script标签中的整体代码

javascript在执行时,先从 macrotasks 队列开始执行,取出第一个 macrotask 放入执行栈执行,在执行过程中,如果遇到 macrotask,则将该 macrotask 放入 macrotask 队列,继续运行执行栈中的后续代码。如果遇到microtask,那么将该microtask放入microtask队列,继续向下运行执行栈中的后续代码。当执行栈中的代码全部执行完成后,从microtasks队列中取出所有的microtask放入执行栈执行。执行完毕后,再从macrotasks 队列取出下一个macrotask放入执行栈。然后不断重复上述流程。这一过程也被称作事件循环(Event Loop)。

javascript就是通过这种机制来实现异步的。主线程会暂时存储I/O等异步操作,直接向下执行,当某个异步事件触发时,再通知主线程执行相应的回调函数,通过这种机制,javascript避免了单线程中异步操作耗时对后续任务的影响。

图解事件循环流程

根据图中描述,一次事件循环的执行步骤如下:

1、从macrotask queue中取出最早的任务

2、在执行栈中执行第一步取出的任务

如果任务中存在microtask,将其压入到microtask queue中

如果任务中存在macrotask,将其压入到macrotask queue中

直到执行完毕

3、执行栈设置为null

4、从macrotask queue中删除执行过的macrotask

5、取出microtask queue中的全部任务,放入执行栈,

如果任务中存在microtask,将其压入到microtask queue中

如果任务中存在macrotask,将其压入到macrotask queue中

注意:这里产生的microtask(也就是microtask产生的microtask )也会在这一步骤中执行。

直到当前microtask queue为空,此步骤结束。

6、执行第一步的操作

实例验证

我们执行如下一段代码,用上面的思路执行,看一下结果是否和预期的一致。

console.log('start')

const interval = setInterval(() => {

console.log('setInterval')

}, 0)

setTimeout(() => {

console.log('setTimeout 1')

Promise.resolve()

.then(() => {

console.log('promise 3')

})

.then(() => {

console.log('promise 4')

})

.then(() => {

setTimeout(() => {

console.log('setTimeout 2')

Promise.resolve()

.then(() => {

console.log('promise 5')

})

.then(() => {

console.log('promise 6')

})

.then(() => {

clearInterval(interval)

})

}, 0)

})

}, 0)

Promise.resolve()

.then(() => {

console.log('promise 1')

})

.then(() => {

console.log('promise 2')

})

按照上面的思路,我们来理一下,预测一下执行结果,看看实际效果是否是这样的。

执行流程:

第一轮:

1、首先这一整段js代码作为一个macrotask先被执行

2、遇到console.log('start'),输出start

3、遇到setInterval,回调函数作为macrotask压入到macrotask queue中,

此时macrotask queue:[setInterval]

4、遇到setTimeout,回调函数作为macrotask压入到macrotask queue中,

此时macrotask queue:[setInterval,setTimeout1]

5、遇到Promise,并且调用了resolve方法,触发了回调,回调作为microtask压入到microtask queue中

此时microtask queue:[promise 1,promise 2]

6、执行栈为空,将microtask queue中的任务放入执行栈

7、执行microtask queue中Promise的回调任务,分别打印promise 1,promise 2

8、执行栈为空,microtask queue为空,开始下一轮事件循环

目前的console中打印内容:

start

promise 1

promise 2

目前macrotask queue:[setInterval,setTimeout1]

第二轮:

1、从macrotask queue中取出最早的任务,这里对应的是第一轮中第3步的回调函数:console.log('setInterval'),输出setInterval

2、setInterval的回调函数作为macrotask压入到macrotask queue中

此时macrotask queue:[setTimeout1,setInterval]

3、执行栈为空,microtask queue为空,开始下一轮事件循环

目前的console中打印内容:

start

promise 1

promise 2

setInterval

目前macrotask queue:[setTimeout1,setInterval]

第三轮:

1、从macrotask queue中取出最早的任务,目前是setTimeout1的回调,将取出的任务放入执行栈执行

2、遇到console.log('setTimeout 1'),输出setTimeout 1

3、遇到Promise,并且调用了resolve方法,触发回调,回调作为microtask压入到microtask queue中

此时microtask queue:[promise 3,promise 4,() => {setTimeout 2}]

4、执行栈为空,将microtask queue中的任务放入执行栈

5、执行microtask queue中Promise的回调任务:

输出promise 3

输出promise 4

将setTimeout 2压入macrotask queue

6、执行栈为空,microtask queue为空,开始下一轮事件循环

目前的console中打印内容:

start

promise 1

promise 2

setInterval

setTimeout 1

promise 3

promise 4

目前macrotask queue:[setInterval,setTimeout2]

第四轮:

1、从macrotask queue中取出最早的任务,这里对应的是setInterval,输出setInterval

2、setInterval的回调函数作为macrotask压入到macrotask queue中

此时macrotask queue:[setTimeout2,setInterval]

3、执行栈为空,microtask queue为空,开始下一轮事件循环

目前的console中打印内容:

start

promise 1

promise 2

setInterval

setTimeout 1

promise 3

promise 4

setInterval

目前macrotask queue:[setTimeout2,setInterval]

第五轮:

1、从macrotask queue中取出最早的任务,目前是setTimeout2的回调,将取出的任务放入执行栈执行

2、遇到console.log('setTimeout 2')输出setTimeout 2

3、遇到Promise,并且调用了resolve方法,触发回调,回调作为microtask压入到microtask queue中

此时microtask queue:[promise 5,promise 6,() => {clearInterval}]

4、执行栈为空,将microtask queue中的任务放入执行栈

5、执行microtask queue中Promise的回调任务:

输出promise 5

输出promise 6

clearInterval清空setInterval计时器

6、执行栈为空,microtask queue为空,macrotask queue为空,任务结束。

最终的console中打印内容:

start

promise 1

promise 2

setInterval

setTimeout 1

promise 3

promise 4

setInterval

setTimeout 2

promise 5

promise 6

通过图片可以看到,结果跟我们的预期一致,在promise2的后面作为方法的返回值,多打印了一个undefined,这个应该好理解的。

这里面有个小问题,就是在不同的环境下(node/浏览器),promise4后面的setInterval表现可能会有差异,这里可能跟setTimeout和setInterval的最小间隔有关,虽然我们写成0ms,但实际上这个最小值是有限制的,现阶段不同组织和不同的js引擎实现机制存在差异,不过这个问题不在本次讨论范围之内了。如果我们将上述代码中setInterval的间隔设置为10,那么整个执行流程将严格符合我们的预期。

有什么用?

后续我们在代码中使用Promise,setTimeout时,思路将更加清晰,用起来更佳得心应手。

在阅读一些源码时,对于一些setTimeout相关的骚操作可以理解的更加深入。

理解javascript中的任务执行流程,加深对异步流程的理解,少犯错误。

总结

js事件循环总是从一个macrotask开始执行

一个事件循环过程中,只执行一个macrotask,但是可能执行多个microtask

执行栈中的任务产生的microtask会在当前事件循环内执行

执行栈中的任务产生的macrotask要在下一次事件循环才会执行

php event loop,理解javascript中的事件循环(Event Loop)相关推荐

  1. JavaScript中的事件循环

    JavaScript是单线程单并发语言 单线程:主程序只有一个线程,即同一时间片段内其只能执行单个任务. 引发的问题: 单线程,意味着任务都需要排队,前一个任务结束,才会执行后一个任务.若前一个任务耗 ...

  2. JavaScipt 中的事件循环(event loop),以及微任务 和宏任务的概念

    说事件循环(event loop)之前先要搞清楚几个问题. 1. js为什么是单线程的? 试想一下,如果js不是单线程的,同时有两个方法作用dom,一个删除,一个修改,那么这时候浏览器该听谁的? 2. ...

  3. 理解JavaScript中的事件

    在很多语言的学习中,"事件"都是一个比较难理解,但是又是一个很重要的概念.JavaScript中的事件处理也是一样,正因为有了事件处理,才会出现Ajax拖动的效果.本文就讨论一下J ...

  4. javaScript中的事件对象event是怎样

    事件对象event,每当一个事件被触发的时候,就会随之产恒一个事件对象event,该对象中主要包含了关于该事件的基本属性,事件类型type(click.dbclick等值).目标元素target(我的 ...

  5. 再次理解javascript中的事件

    一.事件流的概念 + 事件流描述的是从页面中接收事件的顺序. 二.事件捕获和事件冒泡 +    事件冒泡接收事件的顺序: +   事件捕获接收事件顺序: +   IE中的事件流叫事件冒泡,IE中没有事 ...

  6. 技术干货 | JavaScript 之事件循环(Event Loop)

    导读:学过 JavaScript(下文简称 JS) 的都知道它是一门单线程的.非阻塞的脚本语言.单线程意味着,JS 代码在执行的任何时候,都只有一个主线程来处理所有的任务,这也就意味着 JS 无法进行 ...

  7. JavaScript 异步执行的学习笔记 - 什么是事件循环 Event loop?

    原文 使用像 JavaScript 这样的语言进行编程时,最重要但也经常被误解的部分之一是如何表达和操作一段需要某段时间才能完成执行的程序行为. 这不仅仅是从 for 循环开始到 for 循环结束发生 ...

  8. JAVA script 循环 图片_深入分析JavaScript 事件循环(Event Loop)

    事件循环(Event Loop),是每个JS开发者都会接触到的概念,但是刚接触时可能会存在各种疑惑. 众所周知,JS是单线程的,即同一时间只能运行一个任务.一般情况下这不会引发问题,但是如果我们有一个 ...

  9. JavaScript基础:浅聊事件循环(Event LooP)以及微任务,宏任务,DOM渲染

    一直说JavaScript是单线程的执行的(当然也可以通过其它其它的方式异步,本篇暂时不聊). 内核的组成 首先聊一下浏览器的内核组成部分,当然下面也不是全部,而只是说一些常见的. 主线线程 js引擎 ...

最新文章

  1. 参加51CTO学院软考培训,通过后感想
  2. Html 内联元素、外联元素 和 可变元素
  3. C语言 socket listen()函数(socket()函数创建的socket(套接字描述符)默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求)
  4. Python批量导入Excel文件中的不重复数据到SQLite数据库
  5. 干掉13个区块链最常见的Bug!
  6. Some thoughts on dfs
  7. LINUX下查看CPU使用率的命令[Z]
  8. objective-C语言:第一个OC程序
  9. java中的map是什么_Java中Map的使用
  10. 离开学校后如何下载知网论文或外文论文
  11. 视频剪辑怎么学?五大经验分享,入门可参考
  12. Python破解WIFI密码详细介绍
  13. css font html里写,HTML,CSS,font
  14. 高考倒计时100天,用python看看高三党
  15. linux下raid(md)驱动源码解析
  16. JAVA动态代理Porxy
  17. 20160131-basic-linux-command
  18. PHP curl 中文gbk转utf8
  19. 华为折叠x2是鸿蒙系统吗,华为发布折叠旗舰Mate X2:各种黑科技设计,率先升级鸿蒙系统...
  20. 企业微信小助理,企业微信营销软件,企业微信hook协议

热门文章

  1. leetcode 752. Open the Lock | 752. 打开转盘锁(BFS)
  2. DevOps运维开发一体化 - 公开课笔记
  3. 《重构:改善既有代码的设计》阅读笔记
  4. 这么画c语言编程流程图,我想问一下这两个C语言的流程图像图中这么画吗?
  5. postman添加cookie
  6. ChubaoFS:一个面向大规模容器平台的分布式文件系统
  7. C语言:L1-036 A乘以B (5分)
  8. led大屏按实际尺寸设计画面_年会活动要用LED大屏还是投影?专业行家都是看这些数据。...
  9. kali如何取得超级用户权限_如何在 Ubuntu 上为用户授予和移除 sudo 权限 | Linux 中国...
  10. ubuntu建立向windows一样的快捷方式