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

往期文章:

相关推荐
GIS程序媛—椰子20 分钟前
【Vue 全家桶】7、Vue UI组件库(更新中)
前端·vue.js
DogEgg_00127 分钟前
前端八股文(一)HTML 持续更新中。。。
前端·html
ZL不懂前端30 分钟前
Content Security Policy (CSP)
前端·javascript·面试
木舟100933 分钟前
ffmpeg重复回听音频流,时长叠加问题
前端
杜杜的man39 分钟前
【go从零单排】go中的结构体struct和method
开发语言·后端·golang
幼儿园老大*40 分钟前
走进 Go 语言基础语法
开发语言·后端·学习·golang·go
llllinuuu41 分钟前
Go语言结构体、方法与接口
开发语言·后端·golang
cookies_s_s42 分钟前
Golang--协程和管道
开发语言·后端·golang
王大锤439144 分钟前
golang通用后台管理系统07(后台与若依前端对接)
开发语言·前端·golang
为什么这亚子1 小时前
九、Go语言快速入门之map
运维·开发语言·后端·算法·云原生·golang·云计算