事件循环

前言

这篇文件将详细讲解JavaScript中的事件循环机制。
首先,先做两道小题目:

console.log("a");

setTimeout(() => {
    console.log("b");
}, 0);

console.log(c);

答案:a、c、b

console.log("a")

setTimeout(() => {
    console.log("b")
}, 0);

for (let i = 0; i < 1000; i++) {
    console.log("c")
}

答案:a、1000个c、b

如果你做错了,请你继续往下看。如果你做对了但是不知道原理,也请你继续往下看

执行栈

首先你要知道什么是执行栈

执行栈:是一个数据结构,用于存放各种函数的执行环境,每一个函数执行之前,它的相关信息会加入到执行栈。函数调用之前,创建执行环境,然后加入到执行栈;函数调用之后,销毁执行环境。

执行栈的定义看起来很抽象,接下来我会使用画图的方式为你详细讲解执行栈的工作原理

栈:先进先出,后进后出
栈的基本结构如下:
在这里插入图片描述

看一段简单的代码

function a() {
    console.log("a")
    b();
}
function b() {
    console.log("b");
    c();
}
function c() {
    console.log("c")
}
console.log("global");
a();

我们以这段代码为例,讲解执行栈是如何运行这段代码的

在运行代码时,执行栈的底部会有一个全局上下文,js代码执行时会看一句执行一句,首先执行的肯定是console.log(“global”);
此时执行栈中会push一个log函数的上下文对象:
在这里插入图片描述
当console.log(“global”);执行完之后会将log的上下文从执行栈中移除,然后代码继续执行,此时会执行函数a,会向执行栈中添加a函数的上下文,执行函数a中的代码,又会向执行栈中放log函数的上下文
在这里插入图片描述
当console.log(‘a’);执行完之后会将log的上下文从执行栈中移除,然后执行函数b,同理会将b函数的上下文和b函数中的log函数的上下文放入到执行栈中
在这里插入图片描述
之后会将log函数的上下文从执行栈中移除,再执行函数c,此时执行栈为:
在这里插入图片描述
同理,当c函数中的console.log(‘c’);执行完之后会从执行栈中将log的上下文移除,console.log(‘c’);执行完之后函数c中没有其他代码了,所以函数c也就执行完了,将c的上下文从执行栈中移除,同理,b的上下文、a的上下文和全局上下文都会从执行栈中执行,到此为止,这段代码就执行完了。
执行结果为:global、a、b、c

注意:JS引擎永远执行的是执行栈的最顶部

异步函数

前面我们讲到的都是同步函数,运行到他们是会执行放到浏览器的执行栈中执行,但是有一些函数不会立即执行,需要等到某个时机到达后才会执行,这样的函数就叫做异步函数比如事件处理函数。异步函数的执行时机,会被宿主环境控制。

浏览器的宿主环境中包含五个线程:

1.JS引擎:负责执行执行栈的最顶部代码
2.GUI线程:负责渲染页面
3.事件监听线程:负责监听各种事件
4.计时线程:负责计时
5.网络线程:负责网络通信

所以我们平时说的JavaScript是单线程的语言不是说JavaScript只有一个线程,而是说JavaScript只有一个执行线程。

当上面的线程发生了某些事请,如果该线程发现,这件事情有处理程序,它会将该处理程序加入一个叫做事件队列的内存。当JS引擎发现,执行栈中已经没有了任何内容后,会将事件队列中的第一个函数加入到执行栈中执行。

JS引擎对事件队列的取出执行方式,以及与宿主环境的配合,称之为事件循环。

这两句话同样特别的抽象,接下来我们还是用画图的方式解释

<div>
    <button id="btn">点击</button>
</div>

<script>
    document.getElementById("btn").onclick = function A() {
        console.log("按钮被点击了");
    }
</script>

首先,会执行document.getElementById(“btn”),就会调用getElementById这个函数,会向执行栈中添加一个getElementById的上下文
在这里插入图片描述
当为按钮绑定点击事件之后,浏览器的事件监听线程会监听按钮的点击事件,当按钮被点击之后会执行函数A,此时浏览器宿主中会保存函数A
在这里插入图片描述
此时用户点击了按钮,事件监听线程回想函数A放入到事件队列中等待执行,而不是执行放到执行队列中执行
在这里插入图片描述
当执行栈中没有内容时,才会将函数A放到函数A放到执行栈中,执行函数A,之后的过程就和以上所说的过程一样了,函数A的上下文和log的上下文都放到执行栈中执行,执行完之后将上下文都从执行栈中移除,执行栈再次变为空

在这里插入图片描述
事件循环:就是执行栈从事件对列中去除函数,以及与宿主环境的配合
在这里插入图片描述

到此为止,异步函数和同步函数的执行原理都已经介绍完,最后,补充一点:事件队列又分为宏队列与微队列。
当执行栈为空时会先执行微队列中的函数在执行宏队列中的函数

宏队列:macroTask,计时器结束的回调、事件回调、http回调等等绝大部分异步函数进入宏队列
微队列:MutationObserver,Promise产生的回调进入微队列

结尾

这篇文件主要介绍了执行栈执行同步函数与异步函数的执行机制,现在你回头看一个开篇的两道小题,你肯定会有深刻了体会了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值