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

目录

前言 

同步/异步编程模型

同步

异步

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.mfbz.cn/a/36721.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

IP地址定位技术为何如此准确?揭秘背后原理

据最新数据显示,全球互联网用户数量已突破50亿。为确保用户安全和提供个性化服务,IP地址定位技术愈发重要。但你是否好奇,为何IP地址定位如此准确?今天我们将揭秘其背后原理。 IP地址定位技术利用了多种方法来确定用户的地理位置。…

mac苹果电脑,怎么把mkv转换mp4格式

mac苹果电脑,怎么把mkv转换mp4格式?如果你是一名mac苹果电脑的用户,在电脑上下载到mkv格式的视频后会发现它使用起来非常的麻烦,甚至不能直接打开播放。mkv其实也是一种时间比较久远的视频文件格式,但是不知道是什么原…

MAC电脑查看SHA256方式

背景 现在很多网站下载大文件时,以往通过查看文件大小来确定是否下载正确,但是很多情况下,文件下载后大小差不多,但是很多时候却时候出现无法安装的问题,有可能还是下载的文件出现错误,导致文件无法正常使…

研发效能认证学员作品:使用威胁建模进行DevSecOps实践

一、从DevOps到 DevSecOps 作者: 姚圣伟(现就职天津引元科技 天津市区块链技术创新中心) 研发效能(DevOps)工程师认证学员 DevOps 最开始最要是强调开发和运维的协作与配合,至今,已不仅仅涉及开…

【运维工程师学习二】OS系统管理

【运维工程师学习二】OS系统管理 1、操作系统管理2、进程管理3、进程的启动4、进程信息的查看4.1、STAT 进程的状态:进程状态使用字符表示的(STAT的状态码),其状态码对应的含义:4.2、ps命令常用用法(方便查看系统进程&…

stm32(独立看门狗和窗口看门狗)

独立看门狗介绍 什么是看门狗? 在由单片机构成的微型计算机系统中,由于单片机的工作常常会受到来自外界电磁场的干扰,造 成程序的跑飞,而陷入死循环,程序的正常运行被打断,由单片机控制的系统无法继续工作…

Vue3 网络请求——axios 高级用法之 axios 拦截器实战与并发请求

文章目录 📋前言🎯关于拦截器🎯项目创建🎯代码分析🎯补充:并发请求🧩axios.all() 和 Promise.all() 的区别 📝最后 📋前言 Axios 是一个流行的基于 Promise 的 HTTP 客户…

spring系列所有漏洞vulhub复现CVE-2022-22978、CVE-2022-22963、CVE-2022-22965、CVE-2018-1273

文章目录 CVE-2022-22978 Spring-security 认证绕过漏洞漏洞描述:复现: CVE-2022-22963漏洞描述:复现: 提提神Spring框架Data Binding与JDK 9导致的远程代码执行漏洞(CVE-2022-22965)漏洞描述:复现: Spring Data Commo…

机器学习笔记:随机森林

1 集成学习 集成学习通过构建多个学习器采用加权的方式来完成学习任务一般来讲,多个学习器同属于一种模型,比如决策树,线性模型,而不会交叉用多种模型为了保证集成学习的有效性,多个弱分类器之间应该满足两个条件 准确…

【附3.7安装包】python安装包下载及安装(超详细)

python3.7链接:https://pan.baidu.com/s/1Ett3XBMjWhkVOxkOU8NRqw?pwdqz3l 提取码:qz3l 今日资源:Python 适用系统:WINDOWS ​ Python 3.7.0 软件介绍: Python是一款通用型的计算机程序设计语言,Pytho…

4.5 x64dbg 探索钩子劫持技术

钩子劫持技术是计算机编程中的一种技术,它们可以让开发者拦截系统函数或应用程序函数的调用,并在函数调用前或调用后执行自定义代码,钩子劫持技术通常用于病毒和恶意软件,也可以让开发者扩展或修改系统函数的功能,从而…

unbuntu 22.04 安装和卸载企业微信

every blog every motto: You can do more than you think. https://blog.csdn.net/weixin_39190382?typeblog 0. 前言 记录有关在ubuntu22.04上安装和卸载企业微信 以及企业微信无法打开问题处理 1. 正文 1.1 安装 下载wine环境 http://archive.ubuntukylin.com/softwar…

【JMeter】同步定时器Synchronizing Timer集合点功能

LoadRunner 中有一个可以设置集合点的功能,顾名思义是设置多个虚拟用户等待到一个时间点,都到齐集合后一起发请求达到并发的目的 集合点是什么意思呢? 阻塞线程,直到指定的线程数量到达后,再一起释放,可以…

这一次,Python 真的有望告别 GIL 锁了?

Python 中有一把著名的锁——全局解释器锁(Global Interpreter Lock,简写 GIL),它的作用是防止多个本地线程同时执行 Python 字节码,这会导致 Python 无法实现真正的多线程执行。(注:本文中 Pyt…

Java设计模式之结构型-代理模式(UML类图+案例分析)

目录 一、基础概念 二、UML类图 1、静态代理类图 2、动态代理类图 三、角色设计 四、案例分析 1、静态代理 2、JDK动态代理 3、Cglib动态代理 五、总结 一、基础概念 代理模式是一种结构型设计模式,它用一个代理对象来封装一个目标对象,通常…

NP问题的通俗解释

本博客参考: https://zhuanlan.zhihu.com/p/348250098https://zhuanlan.zhihu.com/p/348020672https://zhuanlan.zhihu.com/p/260512272以及相关的1-6。 是什么 NP的全称是Non Deteministic Polynomial,是线性所不能判别的问题的集合。 NP这个东西是…

使用RabbitMQ

使用RabbitMQ 1 Docker安装RabbitMQ 1.1 安装RabbitMQ # 下载含有管理页面的镜像 docker pull rabbitmq:3.8.8-management# 创建容器 # 5672:应用访问端口;15672:控制台Web端口号; docker run -itd \ --namemy-rabbitmq \ --re…

第六章:YOLO v1网络详解(统一的实时目标检测)

(目标检测篇)系列文章目录 第一章:R-CNN网络详解 第二章:Fast R-CNN网络详解 第三章:Faster R-CNN网络详解 第四章:SSD网络详解 第五章:Mask R-CNN网络详解 第六章:YOLO v1网络详解 第七章:YOLO v2网络详解 第八章:YOLO v3网络详解 文章目录 系列文章目录技…

记录一个heatmap.js在strict模式下的bug

ImageData的data属性只读&#xff0c;无法修改 出问题的在原始代码的490行~528行 var img this.shadowCtx.getImageData(x, y, width, height);var imgData img.data;var len imgData.length;var palette this._palette;for (var i 3; i < len; i 4) {var alpha imgD…

springboot项目中引入本地依赖jar包,并打包到lib文件夹中

1.springboot项目中引入本地依赖jar包&#xff0c;并打包到lib文件夹中 描述&#xff1a;下载了第三方相关jar包后&#xff0c;项目中引入本地jar&#xff0c;测试环境正常&#xff0c;打包线上报错提示为找到该jar 原因&#xff1a;应该在/WEB-INF/lib/xxx.jar&#xff0c;被…