一文读懂什么是Event Loop

Event Loop是一种用于处理异步事件和编写非阻塞代码的计算机程序执行模型,它在现代Web开发中占据着极其重要的地位。本文将深入介绍Event Loop的工作原理、任务分类以及应用场景,帮助读者更加全面深入地了解这个概念。

一、工作原理

  1. JavaScript是单线程语言,在执行代码时只能同时处理一个任务。如果JS脚本中执行的任务发生阻塞或耗时较长,就会导致整个页面变得迟缓或无响应。为了解决这个问题,Event Loop应运而生。
  2. Event Loop的工作逻辑很简单:在主线程中不断执行代码和处理事件,如果当前没有事件需要处理,则等待事件的到来。所有的事件都被放置在一个先进先出的队列中,由Event Loop在下一个tick时挨个处理。每个tick中分为宏任务和微任务两种类型的任务。
    • 宏任务通过setTimeout、setInterval、script(整体代码)、I/O等方式添加到事件队列尾部;
    • 微任务则有Promise.then()、process.nextTick、Object.observe等方式添加到事件队列头部;
  3. 每个宏任务执行之前,会先执行所有已经排队的微任务。同样,某一个宏任务也可能包含多个微任务。这种任务队列的管理方式保证了异步任务的执行顺序,同时也保证了主线程能够执行其它的任务而不会被堵塞。

二、任务分类

前面已经简单介绍了宏任务和微任务,下面将这两者进行详细解释。

2.1 宏任务

常见的宏任务有setTimeout/setInterval、I/O和事件等待。JavaScript中的宏任务是基于“事件驱动”的模式来执行的。例如:

console.log('1');

setTimeout(function () {
  console.log('2');
}, 0);

console.log('3');

在这个例子中,先执行的是第一行打印’1’,然后添加了一个定时器事件,再往下输出’3’,最后才是定时器事件的回调函数输出’2’。由于定时器的时间为0毫秒,实际上并没有等待什么时间,而是将回调函数添加至宏任务队列,在下一个tick(巨幕队列末尾)时开始执行。

2.2 微任务

微任务就是宏任务中的一组小任务,其产生的结果可以被宏任务所支配。它们必须在当前tick结束前执行,否则可能导致下一个tick中无法正常执行。常见的微任务有Promise.then()、Process.nextTick Node.js特有API和Object.observe。

需要注意的是,同一个宏任务内部产生的微任务会先于下一个宏任务执行。例如:

console.log('1');

setTimeout(function () {
  console.log('2');
  Promise.resolve().then(function () {
    console.log('3');
  });
}, 0);

Promise.resolve().then(function () {
  console.log('4');
});

console.log('5');

输出的结果为:1,5,4, 2, 3。解析过程如下:

  1. 第一个tick
    • 首先打印出’1’,然后执行Promise.then()微任务,在微任务队列头部添加了输出’4’的任务。
    • 现在宏任务队列中只有一个定时器事件,该事件被分配到下一个tick执行。
  2. 第二个tick
    • 开始执行定时器事件,输出’2’。
    • 接着,该定时器事件中产生了一个Promise.then(),将其添加到本轮的微任务队列中。
    • 当前轮次的所有宏任务执行完毕,开始处理本轮的微任务队列,输出’4’,再输出’3’。
  3. 于是输出的结果为1,5,4, 2, 3。

三、应用场景

  1. Event Loop在Web开发中经常用来处理异步编程。例如,在从服务端获取数据时就需要使用异步的Ajax请求,否则会造成页面等待时间过长,用户体验差,还可能导致浏览器崩溃。而Event Loop的存在,则可以使得前端工程师更加容易地管理这些异步任务。

  2. 在Node.js中也广泛使用了Event Loop来处理I/O操作。Node.js中提供了多种API接口,例如fs、net或者http模块,这些模块都是基于回调函数实现异步操作,避免了阻塞主线程。

  • 总之,无论是前端开发、后端开发还是移动开发,Event Loop都扮演着至关重要的角色。学习掌握Event Loop,对于你实现高效并且流畅的异步计算和优化性能都是非常有帮助的。

四、示例

以下是一些代码示例,用来说明Event Loop的工作机制:

  1. 宏任务和微任务的执行顺序示例
// 同步任务,输出 '1'
console.log('1');

setTimeout(() => {
  // 定时器回调函数,宏任务
  console.log('2 - Macro Task');
  // 添加一个微任务到队列中
  Promise.resolve().then(() => console.log('3 - Micro Task'));
}, 0);
// 添加一个微任务到队列中
Promise.resolve().then(() => console.log('4 - Micro Task'));
// 同步任务,输出 '5'
console.log('5');

上述代码输出结果为:

1
5
4 - Micro Task
2 - Macro Task
3 - Micro Task

解释如下:

  • 先输出 ‘1’
  • 添加 setTimeout 回调函数到宏任务队列中。
  • 添加第一个 Promise 的回调函数(进行微任务)到微任务队列中。
  • 输出 ‘5’
  • 执行微任务队列中的第一个任务,并输出 ‘4 - Micro Task’
  • 开始执行宏任务队列,把排在队首的 setTimeout 回调函数执行,输出 ‘2 - Macro Task’。然后添加 Promise 的回调函数到微任务队列中
  • 执行微任务队列中的第一个任务,并输出 ‘3 - Micro Task’
  1. 在Node.js中使用Event Loop:
const fs = require('fs');

console.log('Start reading a file...'); // 同步输出提示信息

fs.readFile('file.md', 'utf-8', (err, content) => { // 异步读取文件内容
  if (err) {
    console.log('Error:', err); // 如果报错打印错误信息
    return;
  }

  console.log(content); // 异步输出文件内容
});

console.log('Carry on executing...'); // 同步输出提示信息
  • 上面的代码中,Node.js通过fs模块提供了异步读取文件的方法readFile。整个读取文件的过程是异步非阻塞的。程序会在执行readFile之后立即执行输出 ‘Carry on executing…’,然后等到readFile任务完成时,Event Loop再去检测并执行回调函数,并输出读取的文件内容。

  • 综上所述,Event Loop 对于异步编程以及I/O模型等方面都有着广泛应用,是Web开发中必不可少的一部分。

你可能感兴趣的:(javascript,面试题,javascript,前端,Event,Loop)