Open itboos opened 4 years ago
事件循环: 2023.04 更新 事件循环的存在是为了 协调事件、用户交互、脚本、渲染、网络, 由用户代理实现事件循环,每个代理都有一个关联的事件循环,为改代理所独有的。
只有事件循环存在, 就会不断的执行以下步骤:
1、 让 oldestTask 和 taskStartTime 为空。 2、如果事件循环中有一个包含至少一个 可运行任务的任务队列:
可运行任务的定义: 如果任务的文档(document)为 null 或fully active ,则该任务是可运行的
那么:
2.1、让 taskQueue 成为这样一个任务队列, 一般会选择宏任务队列,而不会选择微任务队列
2.2、将 oldestTask 设置为 taskQueue 中的第一个可运行 任务,并将其从 taskQueue 中移除。
2.3、将事件循环的当前运行任务设置为 oldestTask。
2.4、执行 oldestTask
2.5、将事件循环的当前运行任务设置为 null
2.6、执行微任务检查点。
3、更新渲染
4、如果是 Worker 事件循环,则做以下工作: ... 省略
执行微任务检查点的步骤如下:
1、如果事件循环执行微任务检查点为真,则返回。
2、将事件循环的执行微任务检查点设置为 true。
3、当事件循环的微任务队列不为空时:(循环执行此步骤)
3.1、让 oldestMicrotask 为从事件循环的微任务队列中出队的结果。
3.2、 将事件循环的当前运行任务设置为 oldestMicrotask
3.3、运行最旧的微任务
3.4、 将事件循环的当前运行任务设置回 null
4、 Cleanup Indexed Database transactions.(清理 indexed DB 数据库事物)
5、将事件循环的执行微任务检查点设置为 false
其它:
每个事件循环都有一个当前正在运行的任务,它要么是一个任务,要么是 null。最初,这是空的。它用于处理重入。
每个事件循环都有一个微任务队列,这是一个微任务队列 ,最初是空的。微任务是指通过队列微任务算法创建的 任务的通俗方式。
每个事件循环都有一个执行微任务检查点布尔值,最初为 false。它用于防止执行微任务检查点算法的重入调用。
每个窗口事件循环都有一个DOMHighResTimeStamp 最后的渲染机会时间,最初设置为零。
每个窗口事件循环都有一个DOMHighResTimeStamp 最后的空闲周期开始时间,最初设置为零。
要获取窗口事件循环循环的相同循环窗口,请返回其相关代理的 事件循环为循环的所有对象。 Window
源文件:
https://html.spec.whatwg.org/multipage/webappapis.html#event-loops
再谈事件循环
事件循环这个东西, 在
JavaScript
算是一个基础知识但也算是难点了。网络上关于事件循环的文章也是层出不穷,一是事件循环确实在分析代码执行顺序的时候会用到,二则是越来越多的公司在面试的时候会出此类型的题目来考查候选人JavaScript
基础内功了。所以,不管是出于面试的角度还是提高自己js内功来看,掌握事件循环都是收益挺高的一件事情。事件循环,我在刚毕业的时候研究过一段时间,当时没写成文章。所以,一段时间之后,就又忘记了。然后又去看,然后又忘记,然后陷入了一个死循环。更重要的是,每次看不同的文章,都挺费时间的。于是,想着把自己的理解写成文章,一则可能帮助一些同学理解事件循环,二则加深自己的印象,当忘记的时候回顾可以快速想起。
好了,废话说了这么多,下面进入正题。
首先,澄清一个概念,本文讲的事件循环是指 窗口事件循环(
window event loop
), 因为 事件循环在挺多语言中都有,不同的语言,执行顺序也不太一样。首先,我会大致介绍一下事件循环的规范, 然后结合具体的列子来分析。
定义
定义: 为了协调事件,用户交互,脚本,渲染,联网等,用户代理必须使用本节中描述的事件循环。每个代理都有一个关联的事件循环,该循环对于该代理是唯一的。
同源窗口代理(similar-origin-window-agent)的事件循环被称为是 window event loop,也就是本文中讨论的事件循环。
几个重要的术语
可执行任务 如果任务 的文档为null或完全活动,则该任务是可运行的。(为简单起见,我们可以任务 setTimeout 的时间到了,此回调的任务就可以认为是可以执行的任务了)
宏任务 (task/macrotask) 宏任务大致分为以下:
script
(整体代码)setTimeout, setInterval, setImmediate,I/O
UI rendering
ajax 请求不属于宏任务,js 线程遇到 ajax 请求,会将请求交给对应的 http 线程处理,一旦请求返回结果,就会将对应的回调放入宏任务队列,等请求完成执行。
微任务(microtasks ) 也称为 jobs,但 whatwg规范里没有 出现 jobs 这个说法 微任务大概有下面这些:
process.nextTick
Promise
Object.observe
(已废弃)MutationObserver
(html5新特性)栗子
事件循环处理模型
一个事件循环只要存在,就会持续的运行下面的步骤:省略了一些暂时不需要关注的点
taskQueue
是事件循环的任务队列之一,以实现定义的方式进行选择,并约束所选任务队列必须包含至少一个可运行 任务。如果没有此类任务队列,请跳至下面的微任务步骤。(跳至7)oldestTask
是在任务队列的第一个可以运行的任务, 并把它从TASKQUEUE
中删除。oldestTask
taskStartTime
为当前的高分辨率时间。(不关注)oldestTask
的步骤。null
。hasARenderingOpportunity
为false
(不关注)HRT
(不关注)WebWorker 事件循环
, 则...(不关注)微任务检查点
当用户代理要执行微任务检查点时:
true
,则返回。true
。oldestMicrotask
是 从事件循环的微任务队列中出队的结果, 就是出队操作oldestMicrotask
。oldestMicrotask
Note: 这可能涉及调用脚本回调,该回调最终在运行脚本步骤后调用 清理,调用此脚本再次执行微任务检查点算法, 这就是为什么我们使用执行微任务检查点标志来避免重入。rejected promises
.false
。几个重要的概念
task
或者是null
. 起初, 它是null
. 这是用来处理可重入性。microtask
是指一个的口语化方式 的任务,其通过队列 microtask
算法 创建。false
.它 是用来防止执行微任务检查点算法的重入调用。set
而不是queues
, 因为事件循环处理模型的第一步是从选定的队列里 抓取第一个可以执行的任务, 而不是使第一个任务出队。即 宏任务队列不是标准的queue
, 而是 一系列task
的集合。queue
, 执行任务时,就是先取队头的任务进行执行,新的任务放在队尾。太长不想看了(TL;DR)
如果觉得上面的规范和概念太长了, 不想看了或者没时间看的话。可以事件循环可以总结下面的几个成简短的步骤。
script
里的代码,(这里可以看做是一个宏任务) 遇到宏任务就添加到宏任务队列里,遇到微任务就放到微任务队列里。true
,则跳到 3true
事件循环的处理流程的流程图
可以结合流程图看上面的文字描述,当然,有时间的话,还是建议去看下官方文档,链接贴在本文下方。
demo 分析
demo1:
分析:脚本开始执行: 我们假定 当前有一个 宏任务 Set, macroSet: {} 假定此是的微任务队列为 microTaskQueue: []
begin
setTimeout
), 将set1
回调放到 宏任务队列里, 此时,macroSet = { set1 }
setTimeout
), 将set2
回调放到 宏任务队列里, 此时,macroSet = { set1, set2 }
new Promise
, 由于Promise
构造函数执行会立即调用executor
函数,所以,会输出 p1, p1-1。同时, executor 执行了resolve
, 所以, 此promise settled
了,将resolveCb
放入到 微任务队列里, 此时microTaskQueue = [p2]
Promise.resolve
, 根据Promise.resolve(v)
的定义,是创建一个fulfilled
的promise
对象,其值为 v。 然后将回调 函数 p3 放到微任务队列里, 此时 microTaskQueue = [p2, p3]此时任务队列和执行栈大致如下: PS(忽略执行栈里的 setTimeout1, 偷了个懒,拿了一个之前的老图)
----------------- 第二轮循环 分割线 -----------------
----------------- 第三轮循环 分割线 -----------------
总结输出
总结: 从这里,我们可以猜测,一个事件循环里只有一个微任务队列(因为规范里没有说明只有一个,目前根据输出的结果猜测)
奇奇怪怪的 demo2
更复杂一些的情况是有
async await , 和 Promise.resolve
的情况。由于本文是关于事件循环的, 所以先不展开讲了。具体的打算再写一篇关于Promise
的原理,执行顺序,各种调用的文章。whatwg-event-loop规范
promises-spec 规范