JavaScript 事件循环竟还能这样玩!

JavaScript 是一种单线程的编程语言,这意味着它一次只能执行一个任务。为了能够处理异步操作,JavaScript 使用了一种称为事件循环(Event Loop)的机制。
本文将深入探讨事件循环的工作原理,并展示如何基于这一原理实现一个更为准确的 setTimeoutsetInterval

什么是事件循环?

事件循环是 JavaScript 运行时环境中处理异步操作的核心机制。它允许 JavaScript 在执行任务时不会阻塞主线程,从而实现非阻塞 I/O 操作。

为了理解事件循环,首先需要了解以下几个关键概念:

  1. 调用栈(Call Stack)

    • 调用栈是一个 LIFO(后进先出)结构,用于存储当前执行的函数调用。当一个函数被调用时,它会被推入调用栈,当函数执行完毕后,它会从调用栈中弹出。
  2. 任务队列(Task Queue)

    • 任务队列存储了所有等待执行的任务,这些任务通常是异步操作的回调函数,例如 setTimeoutsetInterval、I/O 操作等。当调用栈为空时,事件循环会从任务队列中取出一个任务并将其推入调用栈执行。
  3. 微任务队列(Microtask Queue)

    • 微任务队列存储了所有等待执行的微任务,这些微任务通常是 Promise 的回调函数、MutationObserver 等。微任务队列的优先级高于任务队列,当调用栈为空时,事件循环会优先处理微任务队列中的所有任务,然后再处理任务队列中的任务。
事件循环的工作原理

事件循环的工作原理可以简化为以下几个步骤:

  1. 执行调用栈中的任务

    • JavaScript 引擎会从调用栈中取出并执行最顶层的任务,直到调用栈为空。
  2. 处理微任务队列

    • 当调用栈为空时,事件循环会检查微任务队列。如果微任务队列中有任务,会依次取出并执行,直到微任务队列为空。
  3. 处理任务队列

    • 当调用栈和微任务队列都为空时,事件循环会检查任务队列。如果任务队列中有任务,会取出一个任务并将其推入调用栈执行。
  4. 重复上述步骤

    • 事件循环会不断重复上述步骤,确保所有任务都能被及时处理。
示例

以下是一个简单的示例,展示事件循环的工作原理:

console.log('Start');setTimeout(() => {console.log('Timeout callback');
}, 0);Promise.resolve().then(() => {console.log('Promise callback');
});console.log('End');

输出结果:

Start
End
Promise callback
Timeout callback

解释如下:

  1. 同步任务:首先执行同步任务,console.log('Start')console.log('End') 被推入调用栈并立即执行。
  2. 微任务Promise.resolve().then 创建了一个微任务,该微任务被推入微任务队列。
  3. 任务setTimeout 创建了一个任务,该任务被推入任务队列。
  4. 处理微任务:同步任务执行完毕后,调用栈为空,事件循环检查微任务队列并执行所有微任务,因此输出 Promise callback
  5. 处理任务:微任务队列为空后,事件循环检查任务队列并执行所有任务,因此输出 Timeout callback
为什么 setTimeout 不准确?

JavaScript 中的 setTimeoutsetInterval 是基于事件循环和任务队列的,因此它们的执行时间可能会受到以下几个因素的影响,从而导致不准确:

  1. 事件循环机制

    • JavaScript 是单线程的,所有代码的执行都是在一个事件循环中进行的。事件循环会依次处理任务队列中的任务。
    • 如果前面的任务执行时间较长,或者任务队列中有很多任务,定时器的回调函数就会被延迟执行。
  2. 任务队列的优先级

    • 浏览器的任务队列有不同的优先级,例如用户交互事件、渲染更新等任务的优先级通常高于 setTimeoutsetInterval
    • 这意味着即使定时器到期,如果有其他高优先级任务在执行,定时器的回调函数也会被延迟执行。
  3. JavaScript 引擎的限制

    • JavaScript 引擎通常会对最小时间间隔进行限制。例如,在浏览器环境中,嵌套的 setTimeout 调用的最小时间间隔通常是 4 毫秒。
    • 这意味着即使你设置了一个非常短的时间间隔,实际执行的时间间隔也可能会比你设置的时间更长。
  4. 系统性能和负载

    • 系统的性能和当前负载也会影响定时器的准确性。如果系统负载较高,任务的执行时间可能会被进一步延迟。

为了更直观地理解这一点,可以考虑以下示例:

console.log('Start');setTimeout(() => {console.log('Timeout callback');
}, 1000);const start = Date.now();
while (Date.now() - start < 2000) {// 模拟一个耗时2秒的任务
}console.log('End');

在这个示例中,setTimeout 的回调函数设置为 1 秒后执行,但由于在主线程上有一个耗时 2 秒的任务,导致定时器的回调函数被延迟到这个任务执行完毕后才执行。

因此,实际执行时间会远远超过 1 秒。

实现一个更准确的 setTimeout

为了实现更精确的定时器,可以结合 Date 对象和递归的 setTimeout 来实现更高精度的定时器。

以下是一个实现准时 setTimeout 的例子:

function preciseTimeout(callback, delay) {const start = Date.now();function loop() {const now = Date.now();const elapsed = now - start;const remaining = delay - elapsed;if (remaining <= 0) {callback();} else {setTimeout(loop, remaining);}}setTimeout(loop, delay);
}// 使用示例
preciseTimeout(() => {console.log('This is a precise timeout callback');
}, 1000); // 1秒

在这个实现中:

  1. 获取当前时间 start
  2. loop 函数中不断计算已经过去的时间 elapsed 和剩余时间 remaining
  3. 如果剩余时间 remaining 小于等于 0,就调用回调函数 callback
  4. 如果剩余时间 remaining 大于 0,就使用 setTimeout 递归调用 loop 函数。

这种方法能比直接使用 setTimeout 更精确地执行定时任务。

进一步优化

上面的代码还可以进一步优化,可以考虑使用 requestAnimationFrame 来实现更高精度的定时器。

requestAnimationFrame 是专门为动画设计的,它会在浏览器下一次重绘之前调用指定的回调函数。由于浏览器的重绘通常是每秒 60 次(即每 16.67 毫秒一次),所以使用 requestAnimationFrame 可以实现更高精度的定时器。

以下是使用 requestAnimationFrame 实现的高精度定时器:

function preciseTimeout(callback, delay) {const start = Date.now();function loop() {const now = Date.now();const elapsed = now - start;if (elapsed >= delay) {callback();} else {requestAnimationFrame(loop);}}requestAnimationFrame(loop);
}// 使用示例
preciseTimeout(() => {console.log('This is a precise timeout callback');
}, 1000); // 1秒

在这个实现中,requestAnimationFrame 会在每次浏览器重绘之前调用 loop 函数,从而实现更高精度的定时器。

实现一个更准确的 setInterval

同样地,我们可以通过结合 Date 对象和递归的 setTimeout 来实现更高精度的 setInterval。以下是一个实现准时 setInterval 的例子:

function preciseInterval(callback, interval) {let expected = Date.now() + interval;function step() {const now = Date.now();const drift = now - expected;if (drift >= 0) {callback();expected += interval;}setTimeout(step, interval - drift);}setTimeout(step, interval);
}// 使用示例
preciseInterval(() => {console.log('This is a precise interval callback');
}, 1000); // 每秒

在这个实现中:

  1. 设置预期的下一次执行时间 expected
  2. step 函数中不断计算当前时间 now 和预期时间 expected 之间的偏差 drift
  3. 如果偏差 drift 大于等于 0,就调用回调函数 callback,并更新预期时间 expected
  4. 使用 setTimeout 递归调用 step 函数,并根据偏差 drift 调整下一次调用的时间间隔。
进一步优化

为了进一步优化,可以考虑使用 requestAnimationFrame 来实现更高精度的定时器。requestAnimationFrame 是专门为动画设计的,它会在浏览器下一次重绘之前调用指定的回调函数。由于浏览器的重绘通常是每秒 60 次(即每 16.67 毫秒一次),所以使用 requestAnimationFrame 可以实现更高精度的定时器。

那我们使用 requestAnimationFrame 来实现的高精度 setInterval

function preciseSetInterval(callback, interval) {let expected = performance.now() + interval;function step() {const drift = performance.now() - expected;if (drift >= 0) {callback();expected += interval;}requestAnimationFrame(step);}requestAnimationFrame(step);
}// 使用示例
preciseSetInterval(() => {console.log('This runs every 2 seconds with higher precision');
}, 2000);

总结

事件循环是 JavaScript 处理异步操作的核心机制,通过调用栈、任务队列和微任务队列的协调工作,实现了非阻塞 I/O 操作。

虽然 setTimeout 的定时精度受到事件循环的影响,但通过结合 Date 对象和递归的 setTimeout,或者使用 requestAnimationFrame,可以实现更为准确的定时器。

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

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

相关文章

git-commit-id-plugin maven插件笔记(git commitId跟踪工具)

文章目录 maven依赖git.properties 例子 代码版本管理比较混乱&#xff0c;如何记录呢? 一种是手动记录&#xff0c;也可以实现&#xff0c;显得有点笨。 也可以通过插件。 maven依赖 <plugin><groupId>pl.project13.maven</groupId><artifactId>git…

面试题系列:Python是什么?使用Python有什么好处?你对 PEP 8 有什么理解?

###面试题系列:Python是什么?使用Python有什么好处?你对 PEP 8 有什么理解? 1、Python是什么? Python是一门动态的(dynamic)且强类型(strong)语言 延伸: 1)静态类型语言和动态类型语言的判别的标准 如果类型检查发生在编译阶段(compile time),那么是静态类型语言(s…

【数据分享】水体分布与五级水系和流域矢量数据+2000-2022年植被指数(NDVI)数据(全国/分省/分市)

1. 数据介绍 数据分为3个层次结构&#xff0c;分别为省、地级市、县。其中&#xff0c;省级水体31个&#xff08;不包含香港、台湾等&#xff09;&#xff0c; 地级市水体366个&#xff0c;县级市水体2847个。每一个文件夹中都包含该省、地级市或者县的水体矢量数据、行政边界…

数学建模 —— 灰色系统(4)

目录 什么是灰色系统&#xff1f; 一、灰色关联分析 1.1 灰色关联分析模型 1.2 灰色关联因素和关联算子集 1.2.1 灰色关联因素 1.2.2 关联算子集 1.3 灰色关联公理与灰色关联度 1.3.1 灰色关联度 1.3.2 灰色关联度计算步骤 1.4 广义关联度 1.4.1 灰色绝对关联…

一文读懂GDPR

GDPR将对人们的网络足迹、使用的APP和服务如何保护或利用这些数据产生重大影响。 下面我们将对有关GDPR人们最关心的问题进行解读。 GDPR是什么&#xff1f; 一般数据保护条例&#xff08;General Data Protection Regulation&#xff09;是一项全面的法律&#xff0c;赋予了…

风电Weibull+随机出力!利用ARMA模型随机生成风速+风速Weibull分布程序代码!

前言 随着能源问题日益突出&#xff0c;风力发电等以可再生能源为基础的发电技术越来越受到关注。建立能够正确反映实际风速特性的风速模型是研究风力发电系统控制策略以及并网运行特性的重要基础叫。由于风速的随机性和波动性&#xff0c;系统中的机械设备和电气设备以及电网…

计算机网络⑩ —— Linux系统如何收发网络包

转载于小林coding&#xff1a;https://www.xiaolincoding.com/network/1_base/how_os_deal_network_package.html 1. OSI七层模型 应用层&#xff0c;负责给应用程序提供统一的接口&#xff1b;表示层&#xff0c;负责把数据转换成兼容另一个系统能识别的格式&#xff1b;会话…

深度剖析云边对接技术:探索开放API接口的价值与意义

在当今数字化时代的浪潮中&#xff0c;云边对接与开放API接口成为了塑造行业生态的重要驱动力。随着云计算、物联网和边缘计算等技术的快速发展&#xff0c;传统产业正在迈向数字化转型的关键时刻。而在这个过程中&#xff0c;云边对接技术以及开放的应用程序接口(API)扮演着举…

处理STM32 DMA方式下的HAL_UART_ERROR_ORE错误

1. 检查并调整DMA和UART配置 确保初始化顺序&#xff1a;需要确保USART的CR寄存器UE位开关留到最后打开&#xff0c;即完成USART和DMA的所有配置初始化后再使能USART。这样可以避免初始化顺序不当导致的通信问题。配置合适的DMA缓冲区&#xff1a;确保DMA缓冲区足够大&#xf…

Facebook海外三不限 | 如何降低Facebook频繁被封的风险

本文将讨论Facebook账户被封的原因及降低封禁风险的方法&#xff0c;以维护用户的账户安全和社交乐趣。 1. 常见原因&#xff1a;账户被封通常与发布违反社区标准的内容有关&#xff0c;如仇恨言论、暴力内容、欺诈虚假信息、非法活动、骚扰、版权侵权等。此外&#xff0c;未授…

el-date-picker选择开始日期的近半年

<el-date-pickerv-model"form[val.key]":type"val.datePickerType || daterange":clearable"val.clearable && true"range-separator"~"start-placeholder"开始日期"end-placeholder"结束日期"style&q…

玩转Linux进度条

准备工作&#xff1a; 一.关于缓冲区 首先&#xff0c;咱们先来一段有意思的代码&#xff1a; #include<stdio.h> #include<unistd.h> int main() {printf("you can see me");sleep(5);} 你可以在你的本地运行一下&#xff0c;这里我告诉大家运行结果…

【SAP HANA 33】前端参数多选情况下HANA如何使用in来匹配?

场面描述: 在操作界面经常会出现某个文本框需要多选的情况,然后后台需要根据多选的值进行匹配搜索。 一般处理的情况是: 1、在Java后端动态生成SQL 2、不改变动态SQL的情况,直接当做一个正常的参数进行传递 本次方案是第二个,直接当做一个正常的字符串参数进行传递即…

【面试题-014】Mysql数据库有哪些索引类型?

文章目录 B 树和 B 树区别B 树B 树 mysql聚簇索引和非聚簇索引聚簇索引&#xff08;Clustered Index&#xff09;非聚簇索引&#xff08;Non-Clustered Index&#xff09;总结 MyISAM和InnoDB两种常见的存储引擎区别MySQL的主从同步原理如何确保主从同步的数据一致性&#xff1…

使用C++实现高效的套接字连接池

在现代网络应用中&#xff0c;高效管理网络连接是实现高并发和低延迟的重要因素。下面将详细介绍如何使用C实现一个高效的套接字连接池&#xff0c;以便在需要时快速复用连接&#xff0c;从而提高系统性能和资源利用率。 一、什么是连接池&#xff1f; 连接池是一种管理网络连…

RFID防盗门:守护您的商品资产安全!

在新零售运营管理中&#xff0c;防盗是至关重要的一环。根据美国零售联合会发布的年度零售安全调查&#xff0c;2022年美国零售商损失了创纪录的1121亿美元。其中年度损失最大因素是由外部盗窃导致库存损失和员工内部盗窃造成的。 然而传统零售业商品资产盘点往往依赖人工排查&…

ConcurrentHashMap详解 什么时候CAS什么时候synchronized

jdk:1.7 segment数组hashEntry数组链表实现 jdk版本&#xff1a;1.8&#xff1a;hashEntry数组红黑树实现 1、基本参数 //**1、最大容量** hashmap的最大容量也是这个&#xff0c;菜鸟一面被问到了 private static final int MAXIMUM_CAPACITY 1 << 30;//数组默认为…

《科技与健康》是什么级别的期刊?是正规期刊吗?能评职称吗?

问题解答 问&#xff1a;《科技与健康》期刊万方网可查吗 答&#xff1a;万方、维普可查 问&#xff1a;《科技与健康》是正规期刊吗&#xff1f; 答&#xff1a;万方维普收录的正规期刊。主管单位&#xff1a;长江出版传媒股份有限公司 主办单位&#xff1a;湖北科学技术…

孩子出生后为什么要做听力筛查?

孩子出生后为什么要做听力筛查&#xff1f; 新生儿听力筛查&#xff0c;就是对所有新生儿在尽早的时间&#xff08;出生48小时后&#xff09;进行系统的听力筛查测试。据相关文献报道&#xff0c;在我国&#xff0c;正常分娩的新生儿听力障碍的发生率约为0.1&#xff5e;0.3%&a…

机场专用手持激光驱鸟器原理及优势

在机场的驱鸟工作中&#xff0c;各类驱鸟设备共同构建起一道坚不可摧的防线&#xff0c;以保障航班的安全起降。其中激光驱鸟器以其卓越的性能和显著效果&#xff0c;在机场鸟击防治中发挥着至关重要的作用。 激光驱鸟器&#xff0c;分为大型自动式和小型手持式&#xff0c;其有…