深入理解JavaScript的事件执行机制

作者:小圆点

时间:2026-10-24

浏览:311

本文深入解析JavaScript单线程模型下的异步执行原理,阐述浏览器多线程如何协同工作。详细区分宏任务与微任务的执行优先级,并通过代码示例剖析Async/Await的运行机制及微任务队列的调用顺序,帮助开发者准确排查异步代码执行问题。

文章目录

前言

掌握事件循环机制以及浏览器的运行原理,对于理解JavaScript代码的执行流程以及排查运行时问题具有显著帮助。以下总结了一些关于浏览器事件循环的核心原理及具体示例。

浏览器JS异步执行的原理

  • JavaScript语言本身是单线程的,这意味着在任意同一时刻,它只能执行一个任务。那么,浏览器是如何实现同时处理异步任务的呢?
  • 原因在于浏览器环境是多线程的。当JavaScript代码需要执行异步任务时,浏览器会启动额外的线程来协助处理该任务。
  • 这里提到的“JS单线程”特指执行JavaScript代码的线程仅有一个,即由浏览器提供的JavaScript引擎线程(主线程)。浏览器内部还包含定时器线程、HTTP请求线程等其他线程,这些线程并非用于直接执行JavaScript代码。
  • 例如,当主线程需要发起一个AJAX请求时,它会将该任务委托给浏览器的另一个线程(如HTTP请求线程)去实际发送请求。待请求返回后,再将回调函数中需要执行的JavaScript代码交还给JavaScript引擎线程执行。
  • 由此可见,真正负责发送网络请求的是浏览器,而JavaScript仅负责执行最终的回调处理逻辑。因此,这种异步能力并非JavaScript语言自身实现,而是由浏览器环境提供的支持。

前言

浏览器中的事件循环

执行栈与任务队列

JavaScript在解析代码时,会将同步代码按顺序放入执行栈中,并依次执行栈内的函数。当遇到异步任务时,该任务会被委托给其他线程处理。当前执行栈中的所有同步代码执行完毕后,系统会从任务队列中取出已完成的异步任务回调,并将其加入执行栈继续执行。若再次遇到异步任务,则继续委托给其他线程,如此循环往复。其他异步任务完成后,其回调会被放入任务队列,等待执行栈空闲时取出执行。

前言

宏任务和微任务

根据任务类型的不同,任务队列可分为微任务(micro task)队列和宏任务(macro task)队列。在事件循环过程中,当执行栈中的同步代码执行完毕后,系统会优先检查微任务队列是否有待执行的任务;若无,则继续检查宏任务队列。这一过程不断循环。微任务通常在当前事件循环周期内优先执行,而宏任务则需等待下一次循环。因此,微任务通常比宏任务先执行。此外,微任务队列只有一个,而宏任务队列可能有多个。常见的点击和键盘事件也属于宏任务。

常见宏任务包括:

  • setTimeout()
  • setInterval()
  • UI交互事件
  • postMessage
  • setImmediate() -- nodeJs

常见微任务包括:

  • promise.then()、promise.catch()
  • new MutaionObserver()
  • process.nextTick() -- nodeJs

如下示例:


console.log('同步代码1');
setTimeout(() => {
    console.log('setTimeout')
}, 0)

new Promise((resolve) => {
  console.log('同步代码2')
  resolve()
}).then(() => {
    console.log('promise.then')
})
console.log('同步代码3');

// 最终输出"同步代码1"、"同步代码2"、"同步代码3"、"promise.then"、"setTimeout"

具体分析如下:

  • setTimeout回调和promise.then均属于异步执行,它们将在所有同步代码执行完毕后运行;
  • 尽管promise.then在代码书写顺序上位于setTimeout之后,但其执行优先级更高,因为它是微任务;
  • new Promise构造函数内的代码是同步执行的,而promise.then中的回调才是异步的。

注意:在浏览器环境中,若将setTimeout的延时设置为0,默认延迟时间通常为4ms;而在NodeJS环境中,该默认延迟为1ms。 微任务与宏任务的本质区别:

  • 宏任务特征:包含明确的异步任务及对应的回调;需要其他异步线程的支持。
  • 微任务特征:不包含明确的异步任务,仅包含回调;无需其他异步线程支持。

Async/await的运行顺序

特点

  • 使用async声明的函数仅对其返回值进行了包装,确保无论何种情况都会返回一个Promise对象(非Promise值会被转换为Promise{resolve})。
  • await声明只能用在async函数中。
    • 当执行async函数时,若遇到await声明,系统会先按照常规执行规则执行await后面的内容(需注意:若在执行函数内容时再次遇到await声明,则继续执行该await声明的内容)。
    • 执行完毕后立即跳出当前async函数,转而执行主线程中的其他内容;待主线程执行完毕后,再回到await处继续执行后续代码。

示例


const a = async () => {
  console.log("a");
  await b();
  await e();
};

const b = async () => {
  console.log("b start");
  await c();
  console.log("b end");
};

const c = async () => {
  console.log("c start");
  await d();
  console.log("c end");
};

const d = async () => {
  console.log("d");
};

const e = async () => {
  console.log("e");
};

console.log('start');
a();
console.log('end');

运行结果

前言

个人分析

  • 在当前同步执行环境中,首先执行console.log('start');,输出'start'。
  • 随后遇到同步函数a(),执行a()。
  • a()由async/await构造,函数内部遇到console.log("a")并输出'a'。接着遇到await声明await b(),由于b()为异步函数,系统进入函数b()内部执行(此操作逻辑类似递归处理),并将await b()之后的内容推入微任务队列,可标记为[await b()后]。
  • b()同样由async/await构造,顺序执行时遇到console.log("c start")并输出'c start'。随后遇到await声明await c(),由于c()为异步函数,系统进入函数c()内部执行,并将await c()之后的内容推入微任务队列,此时队列标记为[await c()后, await b()后]。
  • c()由async/await构造,顺序执行时遇到console.log("b start")并输出'b start'。接着遇到await声明await d(),由于d()为异步函数,系统进入函数d()内部执行,并将await d()之后的内容推入微任务队列,此时队列标记为[await d()后, await c()后, await b()后]。
  • 在d()函数中,顺序执行遇到console.log("")并输出'd',随后d()函数运行结束。
  • 此时d()执行完毕,且无可执行的异步函数,系统返回同步环境,继续执行a()之后的内容。
  • 遇到console.log("end")并输出'end'。至此,同步环境中的主线程代码执行完毕,系统开始检查微任务队列中是否有待执行的微任务。
  • 微任务队列中包含[await d()后, await c()后, await b()后],系统开始执行await d()后的内容。
  • await d()后的内容是console.log("c end"),输出'c end'。该部分执行完毕后,系统继续从微任务队列[await c()后, await b()后]中检查,执行await c()后的内容。
  • 执行await c()后的内容,即console.log("b end");,输出'b end'。该部分执行完毕后,系统继续从微任务队列[await b()后]中检查,执行await b()后的内容。
  • await d()后的内容涉及await e(),系统遇到await声明并执行e()。判断发现await e()后无其他运行代码,因此无需将其推入微任务队列。
  • 执行e(),顺序执行console.log("e");,输出'e'。此时e()函数结束。
  • 微任务队列此时为空[],无剩余微任务,执行结束。系统重新进入同步环境。

到此这篇关于深入理解JavaScript的事件执行机制的文章就介绍到这了,更多相关JavaScript 事件执行机制内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章