JS事件循环(Event Loop)

JavaScript设计为单线程以避免复杂的同步问题,主要用途是与用户交互和操作DOM。异步任务通过事件循环机制解决,同步任务在主线程执行,异步任务如setTimeout和Promise注册到不同的任务队列。当主线程执行栈为空时,会先执行微任务队列,再执行宏任务队列,如此循环,形成了JavaScript的EventLoop。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

为什么JavaScript是单线程?

        JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

        所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。

        为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

同步任务和异步任务:

        单线程,就是指一次只能完成一件任务,如果在同个时间有多个任务的话,这些任务就需要进行排队,前一个任务执行完,才会执行下一个任务。但如果有一个任务的执行时间很长,比如文件的读取或者数据的请求等等,那么后面的任务就要一直等待,这就会影响用户的使用体验。
为了解决这种情况,Javascript语言将任务的执行模式分成两种:同步(Synchronous)异步(Asynchronous)

        同步模式:  就是前一个任务执行完成后,再执行下一个任务,程序的执行顺序与任务的排列顺序是一致的、同步的;
        异步模式: 则完全不同,每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行队列上的后一个任务,而是执行回调函数;后一个任务则是不等前一个任务的回调函数的执行而执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。
        

事件循环(Event Loop):

        同步任务异步任务分别进入不同的执行"场所"; 同步任务进入主线程,异步任务进入Event Table并注册回调函数。
        当指定的事情完成时,Event Table会将这个函数移入任务队列(task quene),等待主线程的任务执行完毕;
        当栈中的代码执行完毕,执行栈(call stack)中的任务为空时,就会读取任务队列(task quene)中的任务,去执行对应的回调;
        如此循环,就形成js的事件循环机制(Event Loop)

执行过程:

  1. 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
  2. 遇到异步任务, 进入Event Table并注册回调函数; 等到指定的事件完成(如ajax请求响应返回, setTimeout延迟到指定时间)时,Event Table会将这个回调函数移入Event Queue。
  3. 当栈中的代码执行完毕,执行栈(call stack)中的任务为空时,主线程会先检查micro-task(微任务)队列中是否有任务,如果有,就将micro-task(微任务)队列中的所有任务依次执行,直到micro-task(微任务)队列为空; 之后再检查macro-task(宏任务)队列中是否有任务,如果有,则取出第一个macro-task(宏任务)加入到执行栈中,之后再清空执行栈,检查micro-task(微任务),以此循环,直到全部的任务都执行完成。

在这里插入图片描述

 

在这里插入图片描述 

 

任务队列(task queue) 或 事件队列(event queue):

        SetTimeout和promise都是异步任务,那么它们两个先执行谁呢?
        其实在js中也是有一个机制,就是会分为宏任务和微任务。宏任务和微任务分别存放在不同的event queue,这两个队列分别为macrotack queue和microtack queue.

        任务队列分为: macro-task(宏任务)队列, micro-task(微任务)队列;

        在最新标准中,它们被分别称为task与jobs。

macro-task(宏任务):

  • script(整体代码)

  • setTimeout

  • setInterval

  • setImmediate

  • I/O (比如Ajax操作从网络读取数据)

  • UI render

micro-task(微任务):

  • process.nextTick

  • Promise

  • Async/Await(实际就是promise)

  • MutationObserver(html5新特性)

事件表格(Event Table):

        Event Table 可以理解成一张事件->回调函数 对应表。

        用来存储 JS 中的异步事件 (request, setTimeout, IO等) 及其对应的回调函数的列表。

        当指定的事件完成(如ajax请求响应返回, setTimeout延迟到指定时间)时,Event Table会将这个回调函数移入Event Queue, 即macro-task(宏任务)队列 或 micro-task(微任务)队列。

<think>好的,我现在需要详细解释JavaScript事件循环机制的工作原理。首先,用户提供的引用资料已经涵盖了基本概念和部分运行机制,我需要整合这些信息,并确保结构清晰,符合用户的要求。 首先,根据引用[2],JavaScript是单线程的,这意味着它一次只能处理一个任务。这种设计避免了多线程操作DOM可能导致的竞争问题,但同时也带来了阻塞的风险。为了解决这个问题,JavaScript引入了事件循环机制,如引用[1]和[4]提到的,通过非阻塞I/O和异步处理来管理任务。 接下来,事件循环的基本结构需要明确。引用[4]指出,浏览器是多线程的,DOM事件、AJAX、setTimeout等由单独的线程处理。主线程执行同步代码,遇到异步任务时,将其交给对应的Web API处理。完成后,回调函数被放入任务队列,等待事件循环将它们推入调用栈执行。 任务队列的分类也很重要。引用[4]提到microtasks和macrotasks的区别。Microtasks包括Promise和MutationObserver,而macrotasks包括setTimeout、setInterval和I/O操作。事件循环在每一轮循环中会先处理所有microtasks,再处理一个macrotask,确保高优先级的任务优先执行。 然后,结合引用[3]和[5],需要说明事件循环的具体步骤:执行同步代码、处理microtasks队列、渲染UI(如果需要)、处理macrotasks队列中的一个任务,然后循环往复。例如,当多个setTimeout延时相同时,它们会被合并处理,但执行时仍按顺序进入队列。 最后,要确保回答中正确引用来源,并在末尾生成相关问题。需要检查LaTeX格式是否符合要求,特别是行内公式用$...$,独立公式用$$...$$。同时,保持中文回答,结构清晰,分点解释,帮助用户逐步理解机制。</think>### JavaScript事件循环机制详解 JavaScript的**事件循环Event Loop)**是支撑其异步编程的核心机制,解决了单线程环境下的非阻塞问题。以下是其工作原理的分步解析: --- #### 一、核心概念 1. **单线程模型** JavaScript设计之初是单线程的,即只有一个主线程(Main Thread)执行代码[^2]。这意味着所有任务必须排队执行,无法同时处理多个操作。例如: ```javascript console.log("1"); setTimeout(() => console.log("2"), 0); console.log("3"); // 输出顺序:1 → 3 → 2 ``` 即使`setTimeout`延时为0,其回调仍会等待同步代码执行完毕。 2. **非阻塞的实现** 通过**事件循环**,JavaScript将耗时操作(如网络请求、定时器)交给浏览器或Node.js的其他线程处理,主线程继续执行后续代码。完成后,异步任务的回调被推入任务队列,等待执行[^1][^4]。 --- #### 二、事件循环的组成 1. **调用栈(Call Stack)** 用于执行同步代码,遵循后进先出(LIFO)原则。当遇到异步任务时,将其移交给**Web API**处理。 2. **任务队列(Task Queue)** - **宏任务队列(Macrotask Queue)**:包含`setTimeout`、`setInterval`、I/O操作等。 - **微任务队列(Microtask Queue)**:包含`Promise.then`、`MutationObserver`等。 3. **事件循环流程** 每次循环按以下顺序执行: ```plaintext 1. 执行调用栈中的同步代码。 2. 清空微任务队列中的所有任务。 3. 执行一个宏任务。 4. 重复循环。 ``` 例如: ```javascript setTimeout(() => console.log("宏任务"), 0); Promise.resolve().then(() => console.log("微任务")); console.log("同步代码"); // 输出顺序:同步代码 → 微任务 → 宏任务 ``` --- #### 三、关键机制 1. **微任务优先级高于宏任务** 每次事件循环中,微任务队列会在渲染前被完全清空。这意味着微任务可以阻塞渲染,而宏任务不会。 2. **浏览器渲染时机** 浏览器在每轮事件循环结束后,根据是否需要更新界面决定是否渲染(约60帧/秒)。 3. **Node.js与浏览器的差异** Node.js事件循环分为多个阶段(如`timers`、`poll`、`check`),而浏览器环境更简化[^3]。 --- #### 四、示例分析 ```javascript console.log("Start"); setTimeout(() => console.log("Timeout"), 0); Promise.resolve().then(() => console.log("Promise")); console.log("End"); // 输出顺序: // Start → End → Promise → Timeout ``` **执行步骤**: 1. 同步代码`console.log`依次执行。 2. `setTimeout`回调进入宏任务队列。 3. `Promise.then`回调进入微任务队列。 4. 清空微任务队列,输出“Promise”。 5. 执行宏任务队列中的`setTimeout`回调。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值