理解 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的运行机制,并能够更好地调试和优化我们的代码。

往期文章:

相关推荐
雷特IT17 分钟前
Uncaught TypeError: 0 is not a function的解决方法
前端·javascript
九圣残炎24 分钟前
【springboot】简易模块化开发项目整合Redis
spring boot·redis·后端
长路 ㅤ   39 分钟前
vite学习教程02、vite+vue2配置环境变量
前端·vite·环境变量·跨环境配置
亚里士多没有德7751 小时前
强制删除了windows自带的edge浏览器,重装不了怎么办【已解决】
前端·edge
micro2010141 小时前
Microsoft Edge 离线安装包制作或获取方法和下载地址分享
前端·edge
.生产的驴1 小时前
Electron Vue框架环境搭建 Vue3环境搭建
java·前端·vue.js·spring boot·后端·electron·ecmascript
awonw1 小时前
[前端][easyui]easyui select 默认值
前端·javascript·easyui
爱学的小涛1 小时前
【NIO基础】基于 NIO 中的组件实现对文件的操作(文件编程),FileChannel 详解
java·开发语言·笔记·后端·nio
爱学的小涛1 小时前
【NIO基础】NIO(非阻塞 I/O)和 IO(传统 I/O)的区别,以及 NIO 的三大组件详解
java·开发语言·笔记·后端·nio
北极无雪1 小时前
Spring源码学习:SpringMVC(4)DispatcherServlet请求入口分析
java·开发语言·后端·学习·spring