深入理解js中事件循环,调用栈,消息队列

深入理解js中事件循环,调用栈,消息队列

DansRoh Lv4

概要

本文首先讲解什么是调用栈,什么是消息队列,以及事件循环,它们三者之间的关系。
之后通过document.addEventListener('click', ()=>{console.log(111)})这段代码来理解js是如何执行事件以及事件循环的处理过程。

调用栈?or(执行栈)

调用栈是一个抽象的数据结构,用于存储在代码执行过程中所有激活的函数调用的信息。
每当在程序中调用一个函数时,该函数的执行上下文就会被添加(推送)到调用栈的顶部。
每当函数执行完毕并返回时,其对应的执行上下文就会从栈顶被移除(弹出)。
这个过程遵循后进先出(LIFO)的原则。

消息队列(异步任务队列)

消息队列是一个先进先出(FIFO)的队列,用于存储待处理的消息和事件。
这些消息可以是来自用户的交互(如点击或键盘事件)、网络请求、定时器超时等产生的。
每一个消息都关联着一个函数,该函数的执行被称为一个任务。

事件循环

事件循环(Event Loop)是一个编程模式,它等待并发送消息和事件。
在JavaScript中,事件循环是一个处理程序,用于监视调用栈和消息队列。
如果调用栈是空的,即没有正在执行的代码,事件循环就会检查消息队列。
如果队列中有待处理的消息(来自异步操作如:setTimeout, setInterval, I/O操作,用户交互事件等),事件循环就会从队列中取出一个消息并处理它。
这个处理过程包括调用与消息相关联的回调函数,执行它,然后监视过程重新开始,形成一个循环。

消息队列和调用栈和事件循环之间的关系?

看了上面的概念可能觉得很懵,别急,看以下代码,带你理解消息队列的作用,以及和调用栈之间的关系。

请看代码

1
2
3
4
5
function Fn() {
console.log('我被点击啦,快快变大')
}

document.addEventListener("click", Fn)

上面代码执行过程如下

注册事件监听器

  1. 事件监听器注册:此步骤会同步地向 document 对象注册一个点击(click)事件的监听器。这意味着从这一刻起,JavaScript 运行时环境(如浏览器)将开始监听对 document 的点击事件
  2. 不立即执行回调:此时,回调函数 Fn 并不会立即执行。相反,它被存储下来,等待特定的事件(这里是点击事件)发生。

事件发生

  1. 事件发生:当用户实际点击 document 时,JavaScript 运行时会捕捉到这个点击事件。

事件循环与异步回调

  1. 事件加入队列:捕捉到的点击事件将导致关联的回调函数Fn被加入到一个异步任务队列中(也称为消息队列)。这是因为事件监听器响应机制是异步的:虽然事件本身是在主线程上同步捕捉的,但其回调函数的执行是异步进行的,等待当前正在执行的代码完成以及调用栈清空
  2. 事件循环检查调用栈:事件循环(Event Loop)负责监视调用栈和任务队列(包含微任务和宏任务队列)。当调用栈为空(即没有正在执行的同步代码)时,事件循环会从任务队列中取出任务来执行。
  3. 执行回调函数:在这个例子中,点击事件的回调函数Fn成为了待处理的任务。事件循环将此回调函数取出并放置到调用栈上,因而该函数被执行,控制台上打印出 ‘我被点击啦,快快变大’。

理解调用栈

加深对与调用栈的理解
请看以下的代码:

场景一

1
2
3
4
5
6
7
8
9
10
11
12
13
function Fn1() {
let a;
a = 1;
console.log(a)
}
function Fn2() {
let a;
a = 2;
console.log(a)
}

Fn1();
Fn2();

🙋提问: 当执行代码的过程中,Fn1和Fn2的上下文会同时存在与调用栈中吗?

由于js是单线程的,一次只能执行一个任务。所以,Fn1和Fn2的上下文是不会同时出现在调用栈中的。

场景二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Fn1() {
let a;
a = 1;
console.log(a)
Fn2();
Fn3();
}
function Fn2() {
let a;
a = 2;
console.log(a)
}
function Fn3() {
let a;
a = 3;
console.log(a)
}

Fn1();

这段代码中,Fn1的上下文首先被压入调用栈,
在执行到Fn2时,Fn2的上下文被压入调用栈,执行完Fn2的上下文被调用栈弹出,
之后Fn3被压入调用栈,Fn3被弹出,
最后Fn1的上下文被弹出。

总结

  • 调用栈负责同步执行代码:它按序执行代码块(主要是函数),始终只处理位于栈顶的任务。
  • 消息队列负责异步事件:当外部异步事件(如点击、完成的网络请求)发生时,与这些事件关联的回调函数被放入消息队列。
  • 事件循环协调同步和异步执行:它监视调用栈和消息队列。当调用栈为空,意味着没有正在执行的同步任务时,事件循环会将消息队列中的下一个回调函数(如果有的话)移入调用栈执行。
  • 标题: 深入理解js中事件循环,调用栈,消息队列
  • 作者: DansRoh
  • 创建于 : 2024-05-09 00:00:00
  • 更新于 : 2024-06-24 17:16:22
  • 链接: https://blog.shinri.me/2024/05/09/24_深入理解js中事件循环,执行栈,消息队列/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论