事件循环
事件循环是大多数现代应用程序(如JavaScript运行时环境、Node.js以及许多GUI工具包)用来处理并管理异步操作的核心机制。它允许程序执行非阻塞I/O操作,并且在操作完成时通过回调或者承诺(Promises)通知程序。
工作原理 :事件不断循环去查找和执行任务队列中的任务。当遇到异步操作时 例如:网络请求或者时文件读取就会把其交给系统处理并且同时执行其后续代码。等到这些异步处理完成之后它们的结果就会被加入到任务队列等待事件循环处理。
应用实例 : 在Web开发中,浏览器使用事件循环来处理用户交互(如点击和输入)、异步网络请求等,这里小编就介绍一下JS中的事件循环机制。
1. event loop
JavaScript 的事件循环(event loop)是该语言处理异步代码执行的核心机制。它负责执行异步代码并管理调用栈与任务队列之间的交互,确保异步操作。例如:定时器setTimeout , promises 和 I/O 操作能够在适当的时候被执行。简而言之事件循环使得JavaScript能够处理并操作并无需多线程。
核心概念
-
调用栈(Call Stack) :
- 调用栈是一个后进先出的数据结构,用于跟踪当前正在执行的函数。每当一个函数被调用时,它就会被添加到调用栈中,并在函数执行完毕后从栈中移除。
-
任务队列(Task Queue) :
- 任务队列存储了所有等待被执行的任务。例如,由
setTimeout
或addEventListener
注册的回调函数会被放入任务队列中。
- 任务队列存储了所有等待被执行的任务。例如,由
-
微任务队列(Microtask Queue) :
- 微任务队列包含所有需要尽快执行的任务,如
Promise
的回调、MutationObserver
回调等。微任务队列的优先级高于普通任务队列。
- 微任务队列包含所有需要尽快执行的任务,如
-
事件循环:
- 事件循环不断地检查调用栈是否为空。如果调用栈为空,则会从任务队列或微任务队列中取出任务并将其推入调用栈执行。
宏任务与微任务
在 JavaScript 中,异步任务被分为两类:宏任务和微任务。宏任务主要包括 setTimeout
和 setInterval
等操作,它们的回调函数会被放入一个先进先出(FIFO)队列中等待执行。相比之下,微任务主要由 promises 构成,同样遵循 FIFO 原则,但会在每次执行完同步代码后立即清空队列中的所有微任务。
事件循环的工作流程
事件循环的运行机制可以概括为以下几个步骤:
- 同步任务执行:首先,执行当前的同步任务,直至调用栈为空。
- 微任务队列清空:一旦调用栈为空,事件循环会检查并清空所有的微任务队列,确保这些微任务得到及时处理。
- 页面渲染:如果需要,浏览器会进行页面渲染操作。
- 宏任务队列处理:接着,从宏任务队列中取出一个任务执行。注意,每次只处理一个宏任务,以避免长时间阻塞主线程。
- 进入 idle 回调:最后,若无其他任务待处理,事件循环将进入 idle 状态,并可能触发一些低优先级的任务或垃圾回收等操作。
- 执行顺序:
markdown
1. `console.log('Start')`:输出"Start"。
1. `setTimeout`:设置一个0毫秒的定时器,但它的回调不会立即执行,而是被放到任务队列中。
1. `Promise.resolve().then()`:这是一个微任务,其回调会被放入微任务队列。
1. `console.log('End')`:输出"End"。
1. 调用栈为空,处理微任务队列,输出"Promise"。
1. 处理任务队列,输出"Timeout"。
因此,最终的输出顺序为:"Start", "End", "Promise", "Timeout"。
注意事项
整个 script 本身被视为一个宏任务。此外,使用 async/await
关键字实际上是利用了 promise 实现的一种语法糖,其本质仍属于微任务范畴。当创建一个新的 promise 并调用 .then()
方法时,相应的回调会被添加到微任务队列中等待后续执行。