很多人有个误区,认为微任务快于宏任务,其实是错误的。因为宏任务中包括了
script,浏览器会先执行一个宏任务,接下来有异步代码的话就先执行微任务
浏览器内核一般指的渲染引擎(webkit) 浏览器内核是多线程,在内核控制下各线程相互配合以保持同步,一个浏览器通常由以下常驻线程组成:
- GUI 渲染线程
- JavaScript 引擎线程
- 定时触发器线程
- 事件触发线程
- 异步 http 请求线程
1. GUI 渲染线程
- 主要负责页面的渲染,解析 HTML、CSS,构建 DOM 树,布局和绘制等。
- 当界面需要重绘或者由于某种操作引发回流时,将执行该线程。
- 该线程与 JS 引擎线程互斥,当执行 JS 引擎线程时,GUI 渲染会被挂起,当任务队列空闲时,JS 引擎才会去执行 GUI 渲染。
2. JS 引擎线程
- 该线程当然是主要负责处理 JavaScript 脚本,执行代码。
- 也是主要负责执行准备好待执行的事件,即定时器计数结束,或者异步请求成功并正确返回时,将依次进入任务队列,等待 JS 引擎线程的执行。
- 当然,该线程与 GUI 渲染线程互斥,当 JS 引擎线程执行 JavaScript 脚本时间过长,将导致页面渲染的阻塞。
3. 定时器触发线程
- 负责执行异步定时器一类的函数的线程,如: setTimeout,setInterval。
- 主线程依次执行代码时,遇到定时器,会将定时器交给该线程处理,当计数完毕后,事件触发线程会将计数完毕后的事件加入到任务队列的尾部,等待 JS 引擎线程执行。
4. 事件触发线程
- 主要负责将准备好的事件交给 JS 引擎线程执行。
比如 setTimeout 定时器计数结束, ajax 等异步请求成功并触发回调函数,或者用户触发点击事件时,该线程会将整装待发的事件依次加入到任务队列的队尾,等待 JS 引擎线程的执行。
5. 异步 http 请求线程
- 负责执行异步请求一类的函数的线程,如: Promise,axios,ajax 等。
- 主线程依次执行代码时,遇到异步请求,会将函数交给该线程处理,当监听到状态码变更,如果有回调函数,事件触发线程会将回调函数加入到任务队列的尾部,等待 JS 引擎线程执行。
浏览器中的event loop
执行栈认为是一个存储函数调用的栈结构,遵循先进后出的原则
node中的event loop
Node 中的 Event Loop 和浏览器中的是完全不相同的东西。Node.js 采用 V8 作为 js 的解析引擎,而 I/O 处理方面使用了自己设计的 libuv,libuv 是一个基于事件驱动的跨平台抽象层,封装了不同操作系统一些底层特性,对外提供统一的 API,事件循环机制也是它里面的实现 Node.js 的运行机制如下:
-
V8 引擎解析 JavaScript 脚本。
-
解析后的代码,调用 Node API。
-
libuv 库负责 Node API 的执行。它将不同的任务分配给不同的线程,形成一个 Event Loop(事件循环),以异步的方式将任务的执行结果返回给 V8 引擎。
-
V8 引擎再将结果返回给用户。 外部输入数据-->轮询阶段(poll)-->检查阶段(check)-->关闭事件回调阶段(close callback)-->定时器检测阶段(timer)-->I/O 事件回调阶段(I/O callbacks)-->闲置阶段(idle, prepare)-->轮询阶段(按照该顺序反复运行)...
-
timers 阶段:这个阶段执行 timer(setTimeout、setInterval)的回调
-
I/O callbacks 阶段:处理一些上一轮循环中的少数未执行的 I/O 回调
-
idle, prepare 阶段:仅 node 内部使用
-
poll 阶段:获取新的 I/O 事件, 适当的条件下 node 将阻塞在这里
-
check 阶段:执行 setImmediate() 的回调
-
close callbacks 阶段:执行 socket 的 close 事件回调 详细介绍
timers、poll、check这 3 个阶段,因为日常开发中的绝大部分异步任务都是在这 3 个阶段处理的。
(1) timer
timers 阶段会执行 setTimeout 和 setInterval 回调,并且是由 poll 阶段控制的。
同样,在 Node 中定时器指定的时间也不是准确时间,只能是尽快执行。
(2) poll
poll 是一个至关重要的阶段,这一阶段中,系统会做两件事情
- 回到 timer 阶段执行回调
- 执行 I/O 回调
并且在进入该阶段时如果没有设定了 timer 的话,会发生以下两件事情
-
如果 poll 队列不为空,会遍历回调队列并同步执行,直到队列为空或者达到系统限制
-
如果 poll 队列为空时,会有两件事发生
- 如果有 setImmediate 回调需要执行,poll 阶段会停止并且进入到 check 阶段执行回调
- 如果没有 setImmediate 回调需要执行,会等待回调被加入到队列中并立即执行回调,这里同样会有个超时时间设置防止一直等待下去
当然设定了 timer 的话且 poll 队列为空,则会判断是否有 timer 超时,如果有的话会回到 timer 阶段执行回调。
(3) check 阶段
setImmediate()的回调会被加入 check 队列中,从 event loop 的阶段图可以知道,check 阶段的执行顺序在 poll 阶段之后。
浏览器和 Node 环境下,microtask 任务队列的执行时机不同
- Node 端,microtask 在事件循环的各个阶段之间执行
- 浏览器端,microtask 在事件循环的 macrotask 执行完之后执行