理解 JavaScript 的事件循环

在JavaScript中,事件循环(Event Loop)是一个重要的概念,特别是在一些公司的面试中,这是一道必考题。如果你搞不懂事件循环(Event Loop)的概念,那你绝对算不上一个优秀的程序员,本篇博文,希望能帮助大家搞懂浏览器的 Event Loop。

什么是事件循环

由于 JavaScript 是单线程运行的语言,为了能够处理异步事件 ,引入了事件循环机制。它在JavaScript运行环境中负责管理和调度事件和任务的机制。换句话说,它决定了代码中各个部分的执行顺序。事件循环的主要目标是保证 JavaScript 单线程的特性,即一次只处理一个任务,而不会造成阻塞。

任务队列

在事件循环中,JS引擎会不断地从任务队列中取出任务执行,直到任务队列中没有任务为止。这里就涉及到了 JS 中另外一个知识点:任务队列(Task Queue)

当异步操作完成时,会将对应的回调函数放入任务队列中。而事件循环负责从任务队列中取出任务,并执行它们。这样,JavaScript就可以在等待某个操作完成的同时,继续处理其他任务。

宏任务和微任务

任务队列分为两种类型:宏任务(Macrotask)微任务(Microtask)。在每次执行完一个宏任务后,都会按照特定的顺序执行微任务队列中的所有任务,然后才会进入下一个宏任务。

常见的宏任务(Macrotask):

  • setTimeout
  • setInerval
  • setImmediate
  • requestAnimationFrame
  • I/O

常见的微任务(Microtask):

  • process.nextTick(Node独有)
  • Promise
  • Object.observe
  • MutationObserver

每次只能执行一个宏任务,但可以连续执行多个微任务。

执行过程

JavaScript 事件循环机制可以分为以下几个阶段:

1.执行同步代码

当 JavaScript 引擎开始执行脚本时,会先执行当前线程的同步代码,即按照顺序执行脚本中的每一行代码,直到遇到耗时较长的操作或者异步操作为止。

2.执行宏任务

执行完同步代码后,JavaScript 引擎会从宏任务队列中取出一个任务执行。例如,我们可以使用 setTimeout 函数来延迟一段时间之后执行回调函数:

js 复制代码
console.log('Start');
setTimeout(function() {
  console.log('我是 Async Task ----');
}, 1000);
console.log('End');

在这段代码中,StartEnd 是同步代码,会先执行完毕。接着,setTimeout 函数是一个宏任务,会被放入宏任务队列中等待执行。1 秒钟后,setTimeout 函数所设置的回调函数就会被取出来执行。

3. 执行微任务

在执行完一个宏任务之后,JavaScript 引擎会按照顺序执行微任务队列中的所有任务。常见的微任务包括

例如,我们可以使用 Promise 构造函数来创建一个异步任务:

js 复制代码
console.log('Start');
Promise.resolve().then(function() {
  console.log('我是 Micro Task ----');
});
console.log('End');

在这段代码中,StartEnd 是同步代码,会先执行完毕。接着,Promise.resolve 函数是一个微任务,会被放入微任务队列中等待执行。当宏任务执行完成后,JavaScript 引擎会立即按照顺序执行微任务队列中的所有任务。

Node.js 中的事件循环

Node 的 Event Loop 分为6个阶段,它们会按照顺序反复运行。

  • timers:定时器任务,setTimeout 和 setInterval 等定时器回调函数将会被添加到该队列。
  • pending callbacks:处理一些系统级别的回调函数,例如 TCP 或 UDP 连接上异常关闭时,会产生此类回调函数。
  • idle, prepare:内部使用的任务队列,我们不需要关注。
  • Poll轮询:轮询任务队列,执行 I/O 相关回调,例如文件 I/O、网络 I/O 等,几乎所有的回调都将在此排队等待。
  • check:setImmediate 回调函数的执行队列。
  • close callbacks:close 事件的回调队列,例如 socket.on('close')。

在浏览器事件循环中,每执行完一个宏任务,便要检查并执行微任务队列;而node事件循环中则是在"上一阶段"执行完,"下一阶段"开始前执行微任务队列中的任务。

最后,留一道面试题给大家,看看你有没有真的搞懂JavaScript 的事件循环机制。

js 复制代码
console.log(1);
setTimeout(() => {
  console.log(2);
  process.nextTick(() => {
    console.log(3);
  });
  new Promise((resolve) => {
    console.log(4);
    resolve();
  }).then(() => {
    console.log(5);
  });
});
new Promise((resolve) => {
  console.log(7);
  resolve();
}).then(() => {
  console.log(8);
});
process.nextTick(() => {
  console.log(6);
});
setTimeout(() => {
  console.log(9);
  process.nextTick(() => {
    console.log(10);
  });
  new Promise((resolve) => {
    console.log(11);
    resolve();
  }).then(() => {
    console.log(12);
  });
});

请问上面代码中,输出顺序是什么? 答案&解析

总结

JavaScript事件循环是实现异步编程的重要机制。通过合理地利用宏观任务和微观任务以及事件循环的特性,我们可以编写出高效、响应迅速的JavaScript代码。

了解事件循环的工作原理对于开发者来说是非常重要的,它有助于我们更好地理解JavaScript的运行机制,并能够更好地调试和优化我们的代码。

往期文章:

相关推荐
腾讯TNTWeb前端团队3 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
Asthenia04125 小时前
Spring AOP 和 Aware:在Bean实例化后-调用BeanPostProcessor开始工作!在初始化方法执行之前!
后端
Asthenia04126 小时前
什么是消除直接左递归 - 编译原理解析
后端
Asthenia04126 小时前
什么是自上而下分析 - 编译原理剖析
后端
Asthenia04126 小时前
什么是语法分析 - 编译原理基础
后端
Asthenia04126 小时前
理解词法分析与LEX:编译器的守门人
后端
uhakadotcom6 小时前
视频直播与视频点播:基础知识与应用场景
后端·面试·架构
范文杰6 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪6 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试