说一下事件循环机制
为什么会有事件循环
JS 任务分为两种:同步任务和异步任务。
- 同步任务直接放在主线程上排队依次执行
- 异步任务放到任务队列中,等待执行,任务会先被移动到调用栈,然后主线程执行调用栈的任务。
调用栈是一个栈结构,函数调用会形成一个栈帧,帧中包含当前执行函数的参数和局部变量等上下文信息,函数执行完后,它的执行上下文会从栈中弹出。
JS 是单线程的,只有一个主线程,每次只能做一件事,在 ajax 请求中,主线程在等待 响应的过程中会去做其他事情,浏览器先在事件表注册 ajax 的回调函数,响应回来后回调函数被添加到任务队列中等待执行,不会造成线程阻塞。
检查调用找是否为空以及将某个任务添加到调用找中的过程就是事件循环,这是 JS 实现异步的核心。
浏览器中的事件循环
宏任务和微任务
浏览器端的事件循环中异步队列有两种:
- macro 宏任务队列
- micro 微任务队列
常见的宏任务:setTimeout、setInterval、script 整体代码、I/O 操作、UI 渲染、Mouse events、Keyboard events、Network events、HTML parsing 常见的微任务: new Promise().then()、MutationObserve、Dom muataions - 微任务每次执行会清空队列
requestAnimationFrame
requestAnimationFrame 也属于异步方法,但是这个方法不属于宏任务和微任务。
window.requestAnimationFrame() 告诉浏览器,你希望之星动画,并且要求浏览器在下次绘制之前调用指定的回调函数更新动画。该方法需要传入一个回到函数作为参数,该回调函数会在浏览器下次重绘之前执行。
requestAnimationFrame 是 GUI 渲染之前执行,但在微任务之后 ,不过 requestAnimationFrame 不一定会在当前帧必须执行,由浏览器根据当前的策略自行决定在哪一帧执行。
事件循环过程
- 检查宏任务队列,执行一条宏任务
- 检查微任务队列,执行并清空微任务
- 执行视图更新
Node 中的事件循环
Node.js 采用 V8 作为 js 的解析引擎,而 I/O 处理方面使用了自己设计的 libuv, libuv 是一个基于事件驱动的跨平台抽象层,封装了不同操作系统一些底层特性,对外提供统一接口,事件循环机制是它里面的实现。
Node 的运行机制如下:
- V8 引擎解析 JS 脚本
- 解析后的代码,调用 Node API
- libuv 库负责 Node 接口的执行。它将不同的任务分配给不同的线程,形成一个事件循环,以异步的方式将任务的执行结果返回给 V8 引擎。
- V8 引擎再将结果返回给用户。
六个阶段
- timers 阶段:执行 setTimeout、setInterval 的回调
- I/O callbacks 阶段:处理一些上一轮循环中的少数未执行的 I/O 回调
- idle, prepare 阶段:仅 node 内部使用
- poll 阶段:获取新的 I/O 事件,适当条件下 node 将阻塞在这里
- check 阶段:执行 setImmediate 的回调
- close callbacks 阶段:执行 socket 的 close 事件回调
NodeJs 中宏队列主要有四个
- Timers 队列
- IO 回调度列
- check 队列
- Close 回调队列