js语言是单线程的,同一时刻只能做一件事情,为了协调事件、用户交互、UI渲染和网络请求等行为,防止主线程进入阻塞,eventloop的方案应运而生。eventloop包含两类:一类是基于 Browsing Context,一类是基于Worker,二者是独立的。这里主要说一下基于浏览器端的事件循环。
每个事件循环中都包含一个 currently running task(task或者null),一个microtask queue(微任务队列,初始值为空),一个performing a microtask checkpoint(执行微任务检查点布尔值,初始值是false,用于阻止微任务的可重入调用)。
任务队列(task queue)
事件循环是使用任务队列的机制来实现的。一个eventloop中,可以有一个或多个任务队列(task queue),一个任务队列是一系列有序任务(task)的集合(set)。
注意,microtask queue 不是 task queue
任务(task)
通常,一个任务由以下几个部分组成:
- Steps:任务要完成的工作的一系列步骤。
- A Source:任务源之一,对相关任务进行分组和序列化。
- A Document:与任务关联的文档,对于不在窗口事件循环(window event loop)中的任务,为null。
- A script evaluation environment settings object set:一组环境设置对象,用于跟踪任务期间的脚本计算。
如果任务的文档为null或完全活动(fully active),则该任务是可运行的。
每个任务的任务源(source)都来自特定的任务源。对于每个事件循环,每个任务源都必须与特定的任务队列相关联。
任务源(task source)
任务源用于分离逻辑上不同的任务类型,源自同一个任务源的任务必须放在同一个任务队列中。
任务源包括以下几种:
- The DOM manipulation task source,// DOM操作任务源
- The user interaction task source,// 用户交互任务源
- The networking task source,// 网络相关任务源
- The history traversal task source,// 历史记录任务源
- microtask task source // 微任务任务源
事件循环执行过程
先执行同步代码(script,也是个宏任务),遇到异步任务,将他们的具体执行任务放入对应的任务队列中,等同步代码执行完,查看微任务队列(microtask queue)是否有任务,有就循环执行直到微任务列表被清空,然后,执行ui渲染(ui render),之后,从宏任务队列中获取最先的一个任务放入执行栈执行,重复上述过程。
也就是: 宏任务 -> 微任务 -> 渲染,然后重复
事件循环的运行过程大致如下:
- 执行一个宏任务(执行栈中没有就从宏任务队列中获取)
- 执行过程中如果遇到微任务,则将其添加到微任务的任务队列中
- 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
- 当前宏任务执行完毕后,开始检查渲染,然后GUI线程接管渲染
- 当渲染完毕后,js 线程继续接管,开始下一个宏任务(从事件队列中获取)
同步任务
在主线程上排队执行的任务,只有前一个任务执行完毕之后,才能执行后一个任务。同步任务在主线程上执行时,形成一个执行栈。
包含以下形式:
- 输出
- 变量的声明
- 同步函数等
异步任务
异步任务的回调会放入任务队列中,等待后续被调用。
以下会产生异步任务:
- setTimeout和setInterval
- DOM事件
- Promise
- ajax
- fileReader
- 异步函数等
宏任务
宏任务会加入到任务队列中(task queue),待同步代码执行完毕之后,执行栈从任务队列中获取最先加入的那个任务,放入执行栈执行。
以下产生的都是宏任务:
- I/O
- script(整体代码)
- setTimeout
- setInterval
- UI交互事件
- postMessage
- MessageChannel
- requestAnimationFrame
微任务
由微任务算法产生的任务会被加入到微任务队列(microtask queue)中,在执行栈执行完一个宏任务后,会去检查微任务队列,如果不为空,则执行所有微任务队列中的任务。
以下会产生微任务:
- Promise.then
- MutationObserver
目前先简单总结下,以后再深入了解。如果想深入了解可以去这里。