一、什么是事件循环
主线程(事件循环):执行宏任务/微任务,生成 style tree,生成 layout tree,执行复合操作( Compositior )render tree,render tree 再由渲染线程异步地提交 GPU 渲染,查看事件队列,继续执行下一个宏任务
二、js执行机制
要知道事件循环,先知道js的执行机制
- 首先判断js任务是同步的是还是异步,同步任务会在主线程栈中依次顺序执行
- 异步任务会提交到异步进程处理,异步进程会把异步任务放到任务队列中
- 当主线程的,所有同步任务执行完后,会去任务队列中查看是否有可以执行的异步任务,如果有,拿到主线程中执行
事件循环,就是将异步任务反复从任务队列中,拿到主线程上执行的过程
三、异步任务
异步任务分为 宏任务(macrotask) 与 微任务 (microtask),
当执行完同步代码之后,就会执行位于执行列表之后的微任务,然后再执行事件列表中的宏任务
执行步骤
· 1.进入到script标签,就进入到了第一次事件循环.
· 2.遇到同步代码,立即执行
· 3.遇到宏任务,放入到宏任务队列里.
· 4.遇到微任务,放入到微任务队列里.
· 5.执行完所有同步代码
· 6.执行微任务代码
· 7.微任务代码执行完毕,本次队列清空
· 8.寻找下一个宏任务,重复步骤1
宏任务 -> 微任务队列 -> 渲染 -> 下⼀个宏任务 -> 下⼀个微任务队列 ...
浏览器中的事件循环
浏览器端事件循环中的异步队列有两种:macro(宏任务)队列和 micro(微任务)队列。
宏任务队列可以有多个,微任务队列只有一个。
-
常见的宏任务:setTimeout、setInterval、script(整体代码)、I/O 操作、UI 渲染等。
-
常见的微任务: new Promise().then(回调)、MutationObserver(html5新特性) 等。
过程
当某个宏任务执行完后,会查看是否有微任务队列。如果有,先执行微任务队列中的所有任务,如果没有,会读取宏任务队列中排在最前的任务,执行宏任务的过程中,遇到微任务,依次加入微任务队列。栈空后,再次读取微任务队列里的任务,依次类推。
例如:
Promise.resolve().then(()=>{
console.log('Promise1')
setTimeout(()=>{
console.log('setTimeout2')
},0) })
setTimeout(()=>{
console.log('setTimeout1')
Promise.resolve().then(()=>{
console.log('Promise2') })
},0)
// 结果:Promise1,setTimeout1,Promise2,setTimeout2
结果分析:
-
一开始执行栈的同步任务(这属于宏任务)执行完毕,会去查看是否有微任务队列,上题中存在(有且只有一个),然后执行微任务队列中的所有任务输出Promise1,同时会生成一个宏任务 setTimeout2
-
然后去查看宏任务队列,宏任务 setTimeout1 在 setTimeout2 之前,先执行宏任务 setTimeout1,输出 setTimeout1
-
在执行宏任务setTimeout1时会生成微任务Promise2 ,放入微任务队列中,接着先去清空微任务队列中的所有任务,输出 Promise2
-
清空完微任务队列中的所有任务后,就又会去宏任务队列取一个,这回执行的是 setTimeout2
Node中的事件循环
Node.js的运行机制如下:
- V8引擎解析JavaScript脚本。
- 解析后的代码,调用Node API。
- libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎。
- V8引擎再将结果返回给用户。
浏览器事件循环与node事件循环的区别
两者最主要的区别,浏览器中的微任务是在每个相应的宏任务中执行的,而nodejs中的微任务是在不同阶段之间执行的。