JavaScript 事件循环
0x01 事件循环
-
JavaScript 是单线程的,即同一时间内仅能执行一个操作
-
在单线程中,当一段代码中存在需要等待或触发的任务时,会阻塞线程,影响后续代码的执行,因此需要将代码分为同步和异步,其执行过程如下:
flowchart LR 代码--同步-->JS引擎-->执行栈--立即执行-->执行栈 代码--异步-->s[宿主环境<br/>如浏览器或NodeJS]--触发事件-->任务队列 执行栈--查看是否有异步任务-->任务队列--获取异步任务-->执行栈异步编程方法详见《JavaScript 异步编程 | 博客园-SRIGT》
-
在上述执行过程中,执行栈反复向任务队列查看并获取任务的过程,称为事件循环
0x02 宏任务与微任务
- JavaScript 的异步任务又分为宏任务和微任务
- 宏任务由宿主环境发起,如
Ajax
、fetch
、setTimeout
、setInterval
、<script>
、事件等 - 微任务由 JS 引擎发起,如
async/await
、Object.observe
、process.nextTick
、Promise
的then
与catch
回调方法等
- 宏任务由宿主环境发起,如
- 任务队列分为宏任务队列和微任务队列
- 在事件循环中,优先执行所有微任务,之后执行宏任务
0x03 requestAnimationFrame
-
在事件循环中,分为执行和渲染两步
-
对于以下代码,事实上仅最后一句有效
button.addEventListener("click", () => { div.style.display = "none"; div.style.display = "block"; div.style.display = "none"; div.style.display = "block"; div.style.display = "none"; });
因为所有逻辑代码都执行后才会进入渲染,而非每次变化都会进行渲染
-
-
所有同步和异步代码均在执行阶段
- 如同步代码
while(true){}
在执行阶段会阻塞后续渲染,而递归则不会阻塞
- 如同步代码
-
Chromium 提供的
requestAnimationFrame
(简称 rAF)方法允许代码在渲染阶段的渲染前执行 -
举例:方块先右移 1000px,再左移 500px
-
页面元素与样式
<body> <div style="width: 100px; height: 100px; background-color: black;"></div> <div style="width: 500px; height: 5px; background-color: aqua;"></div> <button>开始</button> <script> // JavaScript 逻辑代码 </script> </body>
-
JavaScript 逻辑代码
const div = document.querySelector("div"); const button = document.querySelector("button"); button.addEventListener("click", () => { div.style.transform = "translateX(1000px)"; div.style.transition = "transform 2s ease-in-out"; requestAnimationFrame(() => { div.style.transform = "translateX(500px)"; }); });
此时方块只会右移 500px,并不符合要求
因为完成执行进入渲染时,先执行 rAF,后渲染,因此此时的代码相当于:const div = document.querySelector("div"); const button = document.querySelector("button"); button.addEventListener("click", () => { div.style.transform = "translateX(1000px)"; div.style.transition = "transform 2s ease-in-out"; div.style.transform = "translateX(500px)"; });
-
解决方法:
const div = document.querySelector("div"); const button = document.querySelector("button"); button.addEventListener("click", () => { div.style.transform = "translateX(1000px)"; requestAnimationFrame(() => { requestAnimationFrame(() => { div.style.transition = "transform 2s ease-in-out"; div.style.transform = "translateX(500px)"; }); }); });
-
-End-