无论是在浏览器环境中还是在node环境中,事件循环之所以存在是因为js是单线程的,js代码并不总是同步执行行的,为了不阻塞线程,很多代码都是通过异步回调的方式执行的,这使js的代码执行顺序被打乱,所以我们需要一种机制去协调各个事件的执行顺序,这种机制就是事件循环
一、宏任务和微任务
事件循环是由宏任务和在执行宏任务期间产生的微任务组成的。完成当前的宏任务之后会立即执行在此期间入队的所有微任务
- 宏任务: script 整体代码、setTimeout、setInterval、setImmediate(node 独有的异步API)、requestAnimationFrame(浏览器独有的webAPI)、IO/UI Render(浏览独有的)
- 微任务: Promise、 async/await、MutationObserver(监听DOM 变化)、Object.oberser、process.next(node 独有)
二、浏览器事件循环机制
- 浏览器事件循环由一个宏任务 + 多个微任务组成
- Heap(堆)和 Stack(栈): js 代码执行时会将不同的的变量存储在内存中的不同位置,会将引用类型的数据(Object, Array, Date, RegExp, Funtion)存放在堆中,将基本类型数据(Number,String, Boolean, Symbol, undefined, null)
- Call Stack(执行栈): js 是单线程的,当一系列方法被调用时会将这些方法放入调用栈中依次执行
- Callback Queue(事件队列): js 代码执行的过程中遇到异步事件不会一直等待异步事件执行完成返回结果,而是会将这个异步事件挂起,继续执行执行栈中的事件,当异步事件的结果返回之后,再将这个事件放到执行栈中执行,然后执行其中的同步代码
5. 浏览器事件循环执行的顺序:
a). 执行一个宏任务(一般开始是整体代码script),如果没有可执行的宏任务则直接处理微任务
b). 执行过程中如果遇到微任务,就将微任务添加到微任务队列中
c). 执行过程中遇到宏任务,就将宏任务添加到宏任务队列中
d). 执行一个宏任务之后,需要检测微任务队列中是否有待执行的任务,如果有的话就将要执行的微任务放入执行栈中执行
e). 检查渲染,由GUI线程接管渲染,进行渲染
f). 渲染完毕,JS线程接管控制权,开始下一个宏任务
三、Node 事件循环机制
node 中的事件循环比浏览器中的事件循环要复杂得过,由6个宏任务 + 6个微任务组成
在了解node事件循环之前,先了解下node中异步操作Api 的分类
-
定时器(Timer queue):
1). setTimeout
2). setInterval -
I/O 操作(Poll queue):
1). 文件读写操作
2). 数据库操作
3). 网络请求 -
Node 独有(Check queue):
1). setImmediate: node 独有的API,被它包裹的回调函数会被添加到check 队列中
2). process.nextTick: 会在事件循环启动之前执行,将回调函数插入到每个tick的头部
node 事件循环执行顺序
a). 执行同步代码
b). 执行 process.nextTick 中的回调
c). 执行微任务队列
d). 启动事件循环
e). 检查Timer queue,达到调用时间时,将timer queue中的回调函数放入调用栈中
f). 检查Poll queue, 在此处会阻塞检查timer queue 和 check queue 中是否有待执行的回调函数,如果有就将这两个队列中的回调放入调用栈中执行,否则会等待I/O操作的执行,当 timer queue 和check queue 中有回调时就继续执行回调函数
g). 检查 Check queue,执行check回调