事件循环其实很简单!

news/2025/11/17 22:50:31/文章来源:https://www.cnblogs.com/haoxiugong/p/19234711

一、概念

JavaScript 是单线程执行(基于执行栈 / 调用栈 call stack),事件循环负责不断地从各种任务队列里取任务执行——以保证异步任务的函数回调按规则有序运行,浏览器环境和 Node.js 环境都使用事件循环,尽管他们的事件循环逻辑并不相同。

之所以函数的执行基于“栈”这种结构,是因为 js 函数允许嵌套,先调用的函数需要等待内部函数的调用执行完毕才能执行,也就是先调用后执行的逻辑,正好满足“栈”这种数据结构。

执行栈/调用栈是针对函数调用来说的,而我们 js 任务的执行依赖于任务队列,先进入队列的任务会先执行,而且一个任务中可能存在多个函数。要注意一个是函数调用的机制,一个是任务执行的机制,不是一回事!

二、基本构件

  • Call Stack(调用栈)​:同步代码入栈执行、执行完出栈。
  • 宏任务(macrotask / task)队列​:例如 setTimeoutsetIntervalsetImmediate(Node)、DOM 事件、I/O 回调、UI 渲染触发等。

    宏任务作为之前的一种笼统叫法,现代浏览器对这些任务做了更细的划分,对他们都统称为了 task,不同的任务具有不同的队列。不过,微任务的概念一直被保留使用。

  • 微任务(microtask / job)队列​:例如 Promise.then/catch/finallyasync+awaitqueueMicrotaskMutationObserver(浏览器)、process.nextTick(Node)。
  • 渲染/绘制阶段(browser)​:在合适时机把更新绘制到屏幕(通常在 macrotask 完成并且 microtasks 已清空之后)。

    主要为了后面对于任务执行和浏览器渲染顺序的理解。

  • 事件循环(event loop)​:不断循环——执行一个 macrotask → 清空所有 microtasks → 执行渲染(若需要) → 下一个 macrotask。

三、浏览器里的执行模型

循环的每一轮(tick)大致顺序:

  1. macrotask queue 取出一个任务并执行(例如页面初始 script)。
  2. 当前任务执行完后,立即运行并清空 ​microtask queue​(每出现一个 microtask,它会被加入队列;直到队列空才返回)。

    microtasks 在同一轮里可能不断产生并被立即处理。

  3. 当 microtasks 清空后,会进行一次 ​渲染/绘制​(如果需要)。
  4. 进入下一轮 macrotask。

结论:​microtask 的优先级高于下一个 macrotask​。

这里多提一嘴“tick”,不知道有多少同学看到这个“tick”,马上就会联想到 Vue 中的 nextTick,其实,他们确实有一定渊源。

事件循环中的 tick :

tick = 一次事件循环的执行周期 = Task → Microtask → Render → 下一 tick

而 Vue.nextTick 作用试讲 DOM 更新后的回调放入微任务队列(或者退化为宏任务),主要是为了解决 DOM 的异步更新导致无法得到最新 DOM。Vue 源码逻辑:

if (Promise) microtask
else if (MutationObserver) microtask // 旧浏览器
else macrotask fallback // setImmediate(IE专属) -> setTimeout(Macrotask,最差)

Vue 官方文档对于 nextTick 的解释是:等待下一次 DOM 更新刷新的工具方法。和事件循环中的 tick 何其相似。

除此之外,对于浏览器渲染和事件循环结合很多同学没有了解过,以下是一个结合浏览器渲染的例子:

<script>
console.log('start');setTimeout(() => console.log('timeout'), 0);Promise.resolve().then(() => console.log('promise'));requestAnimationFrame(() => console.log('raf'));console.log('end');
</script>

在浏览器输出:start end promise raf timeout

解释:

  • 同步:startend
  • microtask: promise
  • 渲染相关:requestAnimationFrame 在下一帧渲染前执行(在 microtasks 清空后,但通常在 macrotask 之前的 render 时机),所以 raftimeout 之前
  • setTimeout 是下一轮 macrotask,所以最后输出。

四、Node.js(libuv)与浏览器的区别

Node 的底层是 libuv,事件循环分多个阶段:

  1. timers(处理 setTimeout/setInterval)
  2. pending callbacks(I/O 回调)
  3. idle, prepare(内部使用)
  4. poll(检索新的 I/O 事件并执行)
  5. check(处理 setImmediate)
  6. close callbacks(socket close 等)

微任务(Promise callbacks)是在每个阶段执行后 ​立即清空​(microtask checkpoint);另外 Node 有 process.nextTick,其优先级甚至高于 microtasks(会在当前阶段马上执行,且会在 Promise microtasks 之前运行)。

我不想放很多面试题去讲解,因为面试题是做不完的,而知识的核心重点就是这些。

上面的理论搞懂了,基本上相关面试题都可以做对。

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

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

相关文章

从0到1:揭秘LLM预训练前的海量数据清洗全流程

读完这篇文章,你将用监督微调(SFT)把一个 1.5B 规模的数学模型在 GSM8K 上的零样本推理正确率从 1.56% → 62.9%,同时把输出格式遵循率从 18.9% → 100%。我们将完整走通数据集下载、Prompt 架构、训练配置和评估方…

Upgrade Your Key Programming: New Style CG A22-3+1 Flip-4BTN Wire Remote for CGDI K2 (5pcs/lot)

The Frustration of Unreliable Key Remotes: A Problem for Mechanics and Car Owners Alike In the bustling world of automotive repair, few issues frustrate European and American mechanics more than unreli…

深入解析:使用 Triton 实现 Flash Attention2 - 让大模型训练飞起来

引言 你是否曾经在训练大型语言模型时,眼睁睁地看着 GPU 内存不断飙升,最终因为 OOM(Out of Memory)错误而前功尽弃?或者在处理长序列时,发现注意力机制的计算时间呈平方级增长,让人望而却步? 如果你有过这样的…

AI技术落地实践

好的,这是一个极具前瞻性的问题,充分体现了您对技术趋势的敏锐度。下面我将详细阐述我们在AI技术落地,特别是前端与AI结合方面的完整思考与实践。8. AI技术落地实践 第一部分:SQL编辑器集成LLM的完整实践 1. 技术选…

Day22flex布局

1.felx的组成<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1…

CF2169A题解

贪心传送门:https://codeforces.com/problemset/problem/2169/A 将数组排序,如下情况:\(11\ 12\ 13\ 14\ 14\ 15\),假设 \(a=14\),我们发现我们无论如何选择只能选取 \(a\) 左边或右边的数,又因为平局不算分,贪…

re.compile为什么能提高速度?

re.compile(pattern, flags=0) 的核心作用是 “编译正则表达式模式,生成可重复使用的 Pattern 对象”——本质是把正则字符串“编译”成正则引擎可直接执行的“字节码”,核心价值是 提升重复使用时的效率 + 简化代码…

从 0 搭建 LLM 不再难!这个 PyTorch 项目帮你吃透大模型底层逻辑

如果你曾想深入理解大语言模型(LLM)的 “五脏六腑”,却被框架封装的黑盒接口、复杂的源码结构劝退;如果你希望亲手实现 Transformer 的每一个组件,而非单纯调用transformers库 —— 那么今天推荐的这个开源项目,…

题解:P8819 [CSP-S 2022] 星战

CSP-S 2022 T3 和哈希 trick你说的对,但是, “不可以,总司令!” 这是一个神秘 trick,它的模板题是 P3560,可以先把这个题写了或者先把星战写了再写模板。 题意简述 题目链接 给出 \(n\) 个点 \(m\) 条边的有向图…

instr在mysql索引中作用是什么

在MySQL中,instr函数并不是直接用于创建或管理索引的。然而,instr函数可以用于查询字符串中的子串位置,这在某些情况下可能与索引的使用相关。instr函数用于返回子字符串在字符串中第一次出现的位置。如果子字符串不…

initrans参数在oracle高并发环境下的作用

initrans 参数在 Oracle 数据库中用于设置数据库实例启动时的事务处理并发控制器的初始数量。这个参数对于高并发环境下的数据库性能至关重要,因为它直接影响到数据库能够同时处理的事务数量。在高并发环境下,多个用…

Java集合之【CopyOnWrite和Collections.synchronizedList()的区别】

CopyOnWriteArrayList 介绍 什么是 CopyOnWriteArrayList 适合读多写少的场景 是一个线程安全的List实现,特点是写时复制 当CopyOnWriteArrayList进行修改操作(如add,set,remove)的时候,会复制原数组的值到创建的新…

20232324 2024-2025-1 《网络与系统攻防技术》实验六实验报告

20232324 2024-2025-1 《网络与系统攻防技术》实验六实验报告1.实验内容 1.1靶机探测:主机、端口及漏洞扫描 通过Metasploit的Aux模块中arp_sweep工具完成主机发现;端口扫描可选用nmap工具,或Metasploit的Aux模块中…

Python调用C++代码

Python调用C++代码 1. extern "C" {} 包裹导出函数 // C++ 中存在名称修饰,通过 extern "C" {} 将C++函数导出为C函数 // 1. 为类的每个函数创建C风格接口,第一个参数为对象指针 // 2. 提供 cre…

复杂状态与数据流管理:分布式定时任务系统的设计

好的,这是一个非常考验系统设计深度的问题。下面我将详细拆解这个“分布式定时任务系统”的设计,重点阐述如何解决可靠性和幂等性这两个核心挑战。复杂状态与数据流管理:分布式定时任务系统的设计 在GM平台中,定时…

【第6章 字符串】Python 字符串常用操作完全教程(含代码演示)

{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Python 字符串常用操作完全教程(含代码演示)\n", "> 基于《Pytho…

DAG-有向无环图-拓扑排序

1. 场景 通过当前节点与依赖节点列表描述一个有向无环图DAG节点依赖问题,适合流程图中节点依赖关系的定义,适合存在明确的依赖关系并且按依赖顺序执行的领域项目管理与任务调度 工作流与审批流程2. 数据描叙name:描…

MySQL EXPLAIN中的key_len:精准掌握索引使用情况

深入解析MySQL执行计划中最关键的指标之一,助你快速定位索引优化点,提升查询性能!同时介绍了key_len计算的核心规则。MySQL系列文章 深入解析MySQL执行计划中最关键的指标之一,助你快速定位索引优化点,提升查询性…

1090 : 分解因数 25-11-17

|DFS|递归| 本题的dfs特点在于搜索的空间是动态的,因此需要找到可以利用到限制下一步递归的条件来进行空 间范围的缩小与框定。本题利用的是分解的最小因数,可以对下一步的遍历框定范围 #include<iostream> #i…

NOIP 模拟赛 9

NOIP 模拟赛总结 NOIP 模拟赛 9调了一整场的 T2,样例全过!只有 40 pts。QxQT1 卡门连续两场 T1 放数据结构了欸数据结构题,直接分块就行。 赛时没算时间复杂度,导致打了个暴力交上去以为是正解。 赛后半小时改完,…