在?聊聊浏览器事件循环机制

目录

前言 

同步/异步编程模型

同步

异步

JS异步模型

调用栈

任务队列

宏任务队列

微任务队列

微任务API

事件循环

队列优先级

混合队列

事件循环实现

总结

参考文章

Event-Loop可视化工具


前言 

JS是单线程语言,在某个时间段只能执行一段代码。这种单线程模型的好处是不会出现多线程的竞态条件和死锁等问题:在多线程中,某个资源同时被其他线程调度时可能会出现执行顺序不确定导致错误,或者资源占用等待这一类的问题。因此JS无法同时处理多任务,为了处理这类任务,JavaScript运行时使用了一种叫事件循环机制的异步编程模型

JS的事件循环机制是一种异步编程模型,其特点是异步非堵塞,它决定了JavaScript的异步执行顺序和运行机制。

JS的事件循环机制由调用栈(Call Stack)、任务队列(Task Queue)、事件循环(Event Loop)组成,它们共同协作实现了JavaScript的异步编程模型,下面我会具体介绍这三个部分以及相关知识

同步/异步编程模型

首先我们熟悉一下同步和异步编程的概念

同步

同步编程模型是一种线性的编程模型,程序必须按照代码的顺序一个接一个地执行任务,直到当前任务执行完毕后,才能执行下一个任务

许多语言有sleep()函数,用于延迟或定时操作,sleep会阻塞当前线程或进程,直到暂停时间结束后才会继续执行下一条语句,这就是同步编程的特点

同步编程模型的优点是简单、直观,易于调试,缺点是程序执行效率比较低,容易阻塞程序的运行

异步

异步编程模型是一种非线性的编程模型,异步模型中任务的执行是非阻塞的,程序不必等待当前任务完成,而是可以继续执行下一个任务,同时等待当前任务的结果

在JS中我们可以同时执行多个异步函数,通过回调,事件,Promise等方式来捕获异步结果,此外,许多语言还引入了async/await的概念用于异步操作

异步编程模型的优点是可以提高程序的执行效率和吞吐量,缺点是比同步复杂,需要处理回调、事件、异常等问题。

JS异步模型

上面我们说到调用栈、任务队列、事件循环共同组成了JS的异步模型,那么我们就来看看其三者之间的关系及工作原理

调用栈

调用栈的作用是存储函数调用的上下文信息,其信息包括函数的参数、局部变量、返回地址等;调用栈是先进后出的结构,每当程序执行一个函数时,该函数会被压入调用栈的顶部,执行结束后会从调用栈中弹出

我们使用可视化工具模拟调用栈的操作

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

 

可以看到,函数在执行时会先压入栈,等完全执行完毕后就会被销毁

任务队列

队列的数据结构与上面的栈不同,采用的是先进先出(FIFO)的数据结构,JS的任务队列包含宏任务队列与微任务队列。之前写过一篇关于任务队列的文章,介绍了一下队列的特点:先进入的数据会被优先处理,后进入的数据则会被推迟处理,直到前面的数据处理完毕

JS有两种任务队列,分别是宏任务队列(Macro Task Queue)和微任务队列(Micro Task Queue)

宏任务队列

宏任务队列包括了所有的异步任务,如setTimeout、setInterval、requestAnimationFrame、UI交互事件等。

我们可以在可视化工具中运行以下下面的代码

setTimeout(function a() { }, 100);
setTimeout(function b() { }, 50);
setTimeout(function c() { }, 0);
function d() { }
setTimeout(function e() { }, 0);
d();

效果如下

可以看到使用setTimeout的函数会按顺序放在TaskQueue中,等主线程的所有任务都执行完成后才会从TaskQueue中取出延时函数或者轮询函数放在调用栈中运行

微任务队列

微任务队列包括了Promise回调函数、MutationObserver等。

我们也使用可视化工具对下面的代码进行模拟操作

Promise.resolve().then(function d() { });
Promise.resolve().then(function c() { });
Promise.reject().catch(function b() { });
function a() { }
a()

Promise的回调函数和宏任务队列类似,使用其定义的函数会先放到微任务队列中,等待主线程任务全部执行完成后,再按顺序将函数压入调用栈进行函数执行

微任务API

此外浏览器还内置了微任务队列的API(queueMicrotask),用于手动创建微任务,用法如下

queueMicrotask(function b() {console.log("B");
});
Promise.resolve().then(function a() {console.log("A");
});

我们同样将其代入到工具中,效果如下:

使用queueMicrotask创建的微任务与promise回调相同,定义时会放在微任务队列中

事件循环

说了这么多终于来到了本文的核心部分:事件循环

JS的事件循环是用于协调调用栈和任务队列的机制,它的运行机制是不断监听调用栈和任务队列的状态,根据一定的规则来决定下一步要执行哪个任务。当调用栈为空时,事件循环会从任务队列中取出一个任务,并将其压入调用栈中执行。当调用栈不为空时,事件循环会等待调用栈中的任务执行完毕,再去检查任务队列是否有任务等待执行,周而复始,形成一个事件帧

队列优先级

结合上面说到事件循环的运行流程,在监听任务队列时会根据某种规则来决定运行方式,此种规则就与宏任务与微任务的优先级有关

看看下面的代码

setTimeout(function a() {});
Promise.resolve().then(function b() {});

我们同样借助可视化工具看看效果

可以看到微任务的优先级是高于宏任务的,在主线程执行完任务后首先执行的是微任务,等微任务队列的任务数为0时再执行宏任务

混合队列

了解了上述的理论后,深入进阶的问题就变得迎刃而解了,我们将宏任务,微任务和主线程的队列混合做个示例看看事件循环的运行流程

思考以下代码的执行顺序

setTimeout(function a() {console.log("A");Promise.resolve().then(function d() {console.log("D");});
}, 0);Promise.resolve().then(function b() {console.log("B");setTimeout(function e() {console.log("E");}, 0);
});
fetch("https://baidu.com").then(function f() {console.log("F");
});
function c() {console.log("C");
}
queueMicrotask(function g() {console.log("G");
});
c();

结果如下

执行顺序是C->B->G->A->D->E->F,是不是和你的答案一样呢?

事件循环实现

经过上述例子的介绍,相信大家对JS的异步模型也有了一定的认识,那么我们使用之前讲到的迭代器实现一下事件循环

class EventLoop {microTaskQueue = []; // 微任务队列macroTaskQueue = []; // 宏任务队列static loop = function* (el) {// 使用迭代器实现事件循环,每次调用next都是一个新的循环while (true) {el.runMicroTasks();el.runMacroTasks();yield;}};queueMicroTask = (task) => {// 新增微任务this.microTaskQueue.push(task);};queueMacroTask = (task) => {// 新增宏任务this.macroTaskQueue.push(task);};runMicroTasks = () => {// 运行微任务while (this.microTaskQueue.length > 0) {this.microTaskQueue.shift()();}};runMacroTasks = () => {// 运行宏任务while (this.macroTaskQueue.length > 0) {this.macroTaskQueue.shift()();}};
}

最后我们运行一下上面的事件循环

const el = new EventLoop();// 创建事件循环
const { queueMacroTask, queueMicroTask } = el;
const { loop } = EventLoop;
const iterator = loop(el);// 创建循环迭代
iterator.next();// 第一次循环
queueMacroTask(function () {// 增加宏任务console.log("B");
});
queueMicroTask(function () {// 增加微任务console.log("A");
});
iterator.next();
queueMicroTask(function () {console.log("E");
});
queueMacroTask(function () {console.log("C");
});
queueMicroTask(function () {console.log("D");
});
iterator.next();
iterator.next();
// A B E D C

至此,我们就实现了一个简单的事件循环机制

总结

本篇文章介绍了同步/异步编程模型,同步堵塞线程,但是逻辑简单,异步非堵塞运行,但是需要对结果做处理;JS的异步模型由调用栈、任务队列、事件循环共同组成,其中事件循环充当了组织者,当主线程的任务都执行完成,则执行微任务队列中的任务,等任务执行完成后再运行宏任务;最后我使用JS实现了一个简单的事件循环,帮助理解。

以上就是文章全部内容了,希望能对你有帮助,如果觉得文章不错的话,还望三连支持一下,感谢!

参考文章

✨♻️ JavaScript Visualized: Event Loop - DEV Community

Event-Loop可视化工具

JS Visualizer 9000

http://latentflip.com/loupe/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/1080.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot 实现 PDF 添加水印有哪些方案?

简介 PDF(Portable Document Format,便携式文档格式)是一种流行的文件格式,它可以在多个操作系统和应用程序中进行查看和打印。在某些情况下,我们需要对 PDF 文件添加水印,以使其更具有辨识度或者保护其版…

.NET 8 Preview 4 中的 ASP.NET Core 更新

作者:Daniel Roth - Principal Program Manager, ASP.NET 翻译:Alan Wang 排版:Alan Wang .NET 8 Preview 4 现已可用,并包括了许多对 ASP.NET Core 的新改进。 以下是本预览版本中的新内容摘要: Blazor 使用 Blazor …

【软考网络管理员】2023年软考网管初级常见知识考点(26)- HTML常见属性标签、表格、表单详解

涉及知识点 Html的概念,html常见标签,html常见属性,html表格,html表单,软考网络管理员常考知识点,软考网络管理员网络安全,网络管理员考点汇总。 原创于:CSDN博主-《拄杖盲学轻声码…

基于matlab使用 YOLO V2深度学习进行多类对象检测(附源码)

一、前言 此示例演示如何训练多类对象检测器。 深度学习是一种强大的机器学习技术,可用于训练强大的多类对象检测器,例如 YOLO v2、YOLO v4、SSD 和 Faster R-CNN。此示例使用该函数训练 YOLO v2 多类室内对象检测器。经过训练的物体检测器能够检测和识…

设计模式—“领域规则”

在特定领域中,某些变化虽然频繁,但可以抽象为某种规则。这时候,结合特定领域,将问题抽象为语法规则,从而给出在该领域下的一般性解决方案。 典型模式有:Interpreter Interpreter 动机 在软件构建过程中,如果某一个特定领域的问题比较复杂,类似的结构不断重复出现,…

SpringBoot + Vue前后端分离项目实战 || 五:用户管理功能后续

系列文章: SpringBoot Vue前后端分离项目实战 || 一:Vue前端设计 SpringBoot Vue前后端分离项目实战 || 二:Spring Boot后端与数据库连接 SpringBoot Vue前后端分离项目实战 || 三:Spring Boot后端与Vue前端连接 SpringBoot V…

数字图像处理(三)

目录 实验六、图像分割方法 实验七、图像识别与分类 实验六、图像分割方法 一、实验目的 了解图像分割技术相关基础知识;掌握几种经典边缘检测算子的基本原理、实现步骤理解阈值分割、区域分割等的基本原理、实现步骤。理解分水岭分割方法的基本原理、实现方法。…

在 Mac 上安装 K8S

本篇文章将介绍如何在 Mac 上使用 minikube 搭建单机版的 Kubernetes。 安装步骤 安装 Docker 安装 docker 主要是用于提供容器引擎。直接下载安装即可。 下载地址 安装 Kubectl 推荐使用 home brew 安装 brew install kubectl可以使用下面的命令查看是否已经安装完毕 …

css实现鼠标悬停时滑出层提示

css实现鼠标悬停时滑出层提示的方法介绍 这是一个简单的鼠标悬停提示特效&#xff0c;类似于alt标签&#xff0c;不过这一种是用纯CSS实现&#xff0c;扩展性好&#xff0c;而且在提示的层里可以加入图片或其它布局&#xff0c;这个要根据你的需要了。 代码如下: <!DOCTYPE…

Flink基础概念及常识

1.flink入门 官方定义&#xff1a;Apache Flink是一个框架和分布式处理引擎&#xff0c;用于在无边界和有边界数据流上进行有状态的计算&#xff0c;Flink能在所有常见集群环境中运行&#xff0c;并能以内存速度和任意规模进行计算。 简言之&#xff0c;Flink是一个分布式的计…

Spring相关API

⭐作者介绍&#xff1a;大二本科网络工程专业在读&#xff0c;持续学习Java&#xff0c;努力输出优质文章 ⭐作者主页&#xff1a;逐梦苍穹 ⭐所属专栏&#xff1a;JavaEE、Spring SpringAPI 1、继承体系2、 getBean() 1、继承体系 上述继承体系中的主要类和接口包括&#xff1…

[Spec] WiFi P2P Discovery

学习资料&#xff1a;Android Miracast 投屏 目录 学习资料&#xff1a;Android Miracast 投屏 P2P discovery Introduction Device Discovery procedures Listen State Search State Scan Phase Find Phase 总结 P2P discovery Introduction P2P发现使P2P设备能够快速…