JS事件循环

目录

  • 概述
    • 1. 堆栈(Call Stack)
    • 2. 堆(Heap)
    • 3. 事件队列(Event Queue)
    • 4. 宿主环境(Host Environment)
  • 事件循环(Event Loop)
  • 微任务和宏任务(Microtasks and Macrotasks)
  • 事件循环的重要性

概述

JavaScript 的事件循环是一种机制,它允许 JavaScript 引擎在执行异步代码时,仍然保持单线程执行环境。因为 JavaScript 最初是为了与用户界面交互以及操作文档对象模型(DOM)而设计的,所以处理异步事件——如用户输入、定时器、网络请求——是非常关键的。

事件循环的工作方式如下:

1. 堆栈(Call Stack)

JavaScript 引擎有一个称为“调用堆栈”的结构,用来追踪正在运行的所有执行上下文。当脚本开始执行时,全局执行上下文(比如,你的主JavaScript文件)进入堆栈。然后,函数调用会被添加到堆栈的顶部。一旦函数执行完成,它就会从堆栈中弹出,控制权回到下面的上下文。

2. 堆(Heap)

这是内存的一部分,用于存储对象实例和闭包等动态分配的数据。

3. 事件队列(Event Queue)

事件队列(或任务队列)是异步事件的待办列表。这些事件可能是用户操作、网络事件、定时器到期等产生的。一旦堆栈清空,事件循环就开始工作,检查事件队列的第一个事件。

4. 宿主环境(Host Environment)

宿主环境(Host Environment)指的是JavaScript代码执行的上下文环境,这个环境提供了额外的API和对象供JavaScript代码使用,但这些并不是JavaScript语言本身的一部分。不同宿主环境下,JavaScript能做的事情大相径庭。JavaScript最初被设计为在浏览器中运行,但现在已经超出了这个范围。本次的宿主环境主要就是浏览器环境,其他诸如Node.jsElectronReact Native等。

事件循环(Event Loop)

事件循环的主要职责是监视调用堆栈和事件队列。如果调用堆栈是空的(意味着没有正在执行的代码),事件循环就会取出事件队列中的第一个事件,并将与之关联的回调推送到调用堆栈中去执行。

以下是事件循环的简化模型:

  1. 执行同步代码,这些代码加入到调用堆栈中。
  2. 执行任何微任务(microtask),比如 Promise 回调。
  3. 需要渲染的话,在这个阶段可能会执行渲染更新(在浏览器中)。
  4. 如果事件队列中有等待的宏任务(macrotask),如 setTimeoutsetIntervalI/O 操作,将第一个宏任务推送到调用堆栈。
  5. 返回第二步,循环这个过程。

事件循环(EventLoop):掌握后知道 JS 是如何安排和运行代码的

请回答下面 2 段代码打印的结果,并说明原因

console.log(1)
setTimeout(() => {
  console.log(2)
}, 2000)
console.log(1)
setTimeout(() => {
  console.log(2)
}, 0)
console.log(3)

1.作用:事件循环负责执行代码,收集和处理事件以及执行队列中的子任务

2.原因JavaScript 单线程(某一刻只能执行一行代码),为了让耗时代码不阻塞其他代码运行,设计了事件循环模型

3.概念:执行代码和收集异步任务的模型,在调用栈空闲,反复调用任务队列里回调函数的执行机制,就叫事件循环

/**
 * 目标:阅读并回答执行的顺序结果
*/
console.log(1)
setTimeout(() => {
  console.log(2)
}, 0)
console.log(3)
setTimeout(() => {
  console.log(4)
}, 2000)
console.log(5)

JS事件循环_第1张图片

微任务和宏任务(Microtasks and Macrotasks)

在事件循环中还区分了微任务(microtask)和宏任务(macrotask)。

  • 宏任务是引擎处理的任务的主要单元,例如setTimeoutsetIntervalI/OUI渲染等,由浏览器环境执行的异步代码。
  • 微任务是需要在当前执行上下文结束后立即执行的任务,但在执行下一个宏任务之前。例如,Promise 的回调就属于微任务,由 JS 引擎环境执行的异步代码。

微任务总是在当前宏任务结束后,以及在取出并执行下一个宏任务之前执行。微任务队列会一直执行,直到清空为止。这意味着微任务可以连续不断地添加和执行新的微任务,它们将在下一个宏任务之前执行,这可能会导致长时间不运行宏任务。

宏任务和微任务具体划分

任务(代码) 执行所在环境
JS脚本执行事件 (script) 浏览器
setTimeout/setInterval 浏览器
AJAX请求完成事件 浏览器
用户交互事件等 浏览器
Promise对象.then() JS 引擎

Promise 本身是同步的,而thencatch回调函数是异步的

事件循环模型

具体运行效果,参考动画或者视频

/**
 * 目标:阅读并回答打印的执行顺序
*/
console.log(1)
setTimeout(() => {
  console.log(2)
}, 0)
const p = new Promise((resolve, reject) => {
  resolve(3)
})
p.then(res => {
  console.log(res)
})
console.log(4)

JS事件循环_第2张图片

注意:宏任务每次在执行同步代码时,产生微任务队列,清空微任务队列任务后,微任务队列空间释放!

下一次宏任务执行时,遇到微任务代码,才会再次申请微任务队列空间放入回调函数消息排队

总结:一个宏任务包含微任务队列,他们之间是包含关系,不是并列关系

事件循环的重要性

事件循环使得JavaScript可以执行非阻塞代码,提供了一种在单线程环境中处理并行操作的方法。这就是为什么即使JavaScript引擎在其核心是单线程,它仍然能够处理像Web服务器那样的高并发工作。理解事件循环及其各个部分是高效地写出性能良好,并发行为正确的JavaScript代码的关键。

你可能感兴趣的:(前端,javascript,开发语言,ecmascript)