Event Loop 事件循环简介

简介: Event Loop 事件循环简介

1、Event Loop?

Event Loop 其实也是在面试中经常会出现的一个题,前端程序员回答不上来是正常的,因为 Event Loop 是 C++ 实现的,实现原理和 JavaScript 没什么关系,我个人在之前的面试中遇到这个题的时候,也是看一些 C++ 程序员对 Event Loop 的分析,如果你想对 Event Loop 有一个深入的了解,那这里推荐你一篇 Event Loop 的官方文档,这有中文版:Event Loop。


在了解 Event Loop 之前我们可能需要一点计算机操作系统的知识,比如:当开发人员在电脑键盘上敲击了 a 键,那操作系统是如何知道的呢?大概是这样的,当 a 键盘被按下时,a 键的信息会通过键盘内的电路传递给操作系统,然后操作系统再通知浏览器,浏览器在得到 a 键的信息之后,就会将 a 显示到响应的位置。一句话,浏览器会接收到操作系统传递给它的事件。


那什么是 Event 事件?比如 setTimeout(fun1, delay) 在 delay 到时后需要执行的回调函数 fun1 就是事件,或者 fs.readFile("/1.txt', fun2) 文件读取后执行的 fun2 也是事件,再或者 http 网络请求相关的 server.on('close', fun3) ,在服务器关闭时执行的 fun3 也是事件。


那什么是 Loop 循环?Loop 就是循环的意思,比如我们学习编程过程中的 for、while 循环,它们是一个道理的。但是由于 Event 事件是分优先级的,所以在处理的时候也是要分先后顺序的,Node.js 就是按照顺序来轮询每种事件的。


Event Loop?回到上述所说的 Event 事件的问题,假如上述的场景,操作系统同时触发了三种事件,那 Node 会怎么办?答案是这几种事件有一个优先级分类,然后根据优先级排序执行,这就是 Event Loop 对事件处理顺序的管理。那这个顺序是怎样的呢?

2、Event Loop 顺序?

JavaScript 是单线程的,有了 event loop 的加持,Node.js 才可以非阻塞地执行 I/O 操作,把这些操作尽量转移给操作系统来执行。


浏览器使用了 V8 自带的 Event Loop 事件循环机制,它只有宏任务和微任务之间的循环,而 Node.js 是基于 libuv 自己做了一个 Event Loop,它的事件循环就相对比较复杂。首先看一下浏览器的事件循环:


JS 的任务队列分 macro-task(宏任务)与 micro-task(微任务),在最新标准中,它们被分别称为task与jobs。

macro-task 包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。

micro-task 包括: process.nextTick, Promise., Object.observe(已废弃), MutationObserver(html5新特性)


Node.js 的事件循环分为六个阶段,如下图所示:其中每个方框都是 event loop 中的一个阶段。每个阶段都有一个 先入先出队列,这个队列存有要执行的回调函数(注:存的是函数地址)。不过每个阶段都有其特有的使命,一般来说,当 event loop 达到某个阶段时,会在这个阶段进行一些特殊的操作,然后执行这个阶段的队列里的所有回调。

   ┌───────────────────────────────────┐
┌─>│        timers 检查计时器的callback │
│  └──────────┬────────────────────────┘
│  ┌──────────┴────────────────────────┐
│  │     I/O callbacks                 │
│  └──────────┬────────────────────────┘
│  ┌──────────┴────────────────────────┐
│  │     idle, prepare                 │
│  └──────────┬────────────────────────┘      ┌───────────────┐
│  ┌──────────┴────────────────────────┐      │   incoming:   │
│  │         poll 轮询,检查系统事件     │<─────┤  connections, │
│  └──────────┬────────────────────────┘      │   data, etc.  │
│  ┌──────────┴────────────────────────┐      └───────────────┘
│  │      check 检查 setImmediate 回调  │
│  └──────────┬────────────────────────┘
│  ┌──────────┴────────────────────────┐
└──┤    close callbacks                │
   └───────────────────────────────────┘

timer 阶段:用于监测是否含有计时器的回调事件,如果有的话就执行,没有的话就进入下一阶段。


I/O callbacks:监测是否含有其他的回调,比如 TCP 报错,如果一个 TCP socket 开始连接时出现了 ECONNREFUSED 错误,一些 *nix 系统就会(向 Node.js)通知这个错误。这个通知就会被放入 I/O callbacks 队列。


idle,prepare:空闲阶段,一般用来清理一下内存相关,忽略不计。


poll:轮询,检查系统事件。这个阶段用来处理大部分事件,比如读文件,处理 http 请求等。


check:检查 setImmediate 回调,如果有的话就执行,没有的话就进入下一阶段。


close callbacks:执行关闭事件的回调函数,如 socket.on('close', fun) 里的 fun。


一个 Node.js 程序结束时,Node.js 会检查 event loop 是否在等待异步 I/O 操作结束,是否在等待计时器触发,如果没有,就会关掉 event loop。需要注意的是:大部分时间,Node.js 都停在 poll 轮询阶段,大部分事件都在 poll 阶段被处理,如文件、网络请求。

3、setImmediate() vs setTimeout()

setImmediate 和 setTimeout 很相似,但是其回调函数的调用时机却不一样。setImmediate() 的作用是在当前 poll 阶段结束后调用一个函数。setTimeout() 的作用是在一段时间后调用一个函数。这两者的回调的执行顺序取决于 setTimeout 和 setImmediate 被调用时的环境。


如果 setTimeout 和 setImmediate 都是在主模块(main module)中被调用的,那么回调的执行顺序取决于当前进程的性能,这个性能受其他应用程序进程的影响。举例来说,如果在主模块中运行下面的脚本,那么两个回调的执行顺序是无法判断的。但是,如果把上面代码放到 I/O 操作的回调里,setImmediate 的回调就总是优先于 setTimeout 的回调。

/* timeout_vs_immediate.js*/                      /* timeout_vs_immediate.js*/
setTimeout(() => {                                const fs = require('fs');
  console.log('timeout');                         fs.readFile(__filename, () => {
}, 0);                                                setTimeout(() => {
                                                          console.log('timeout');
setImmediate(() => {                                  }, 0);
  console.log('immediate');                           setImmediate(() => {
});                                                       console.log('immediate');
                                                      });
/*运行结果可能不同: */                             });
$ node timeout_vs_immediate.js                    //运行结果皆为:
timeout                                           $ node timeout_vs_immediate.js
immediate                                         immediate
                                                  timeout
$ node timeout_vs_immediate.js
immediate
timeout

setImmediate 的主要优势就是,如果在 I/O 操作的回调里,setImmediate 的回调总是比 setTimeout 的回调先执行。

目录
相关文章
|
11天前
|
存储 JavaScript 前端开发
JS的执行原理,一文了解Event Loop事件循环、微任务、宏任务
了解JavaScript的事件循环和任务队列对于处理异步任务至关重要。事件循环由主线程和任务队列组成,当主线程执行完同步任务后,会检查任务队列,按顺序执行宏任务和微任务。宏任务包括`setTimeout`等,微任务如`Promise`的回调。在实际开发中,事件循环保证了代码的非阻塞执行,提高了用户体验。例如,`setTimeout`的回调会在当前宏任务结束后,所有微任务执行完才会执行。理解这一机制对于解决面试中的异步问题非常有帮助。
13 0
JS的执行原理,一文了解Event Loop事件循环、微任务、宏任务
|
8月前
|
移动开发 JavaScript 前端开发
说说你对事件循环event loop的理解?
说说你对事件循环event loop的理解?
72 0
|
7月前
|
JavaScript 前端开发
关于 JavaScript 事件循环 Event Loop 的一些理解
关于 JavaScript 事件循环 Event Loop 的一些理解
44 0
|
7月前
|
JavaScript Java
event loop async await 事件循环机制
event loop async await 事件循环机制
31 0
|
8月前
|
JavaScript
【说说你对事件循环event loop的理解】
【说说你对事件循环event loop的理解】
|
8月前
|
Web App开发 消息中间件 JavaScript
事件循环(even loop)原理解析
1,要弄懂事件循环首先要了解线程和进程 通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度。 2, 浏览器是一个多进程多线程的应用程序,内部工作很复杂,它的进程主要包含 浏览器进程 网络进程 渲染进程 ① 浏览器进程: 负者BOM界面展示以
92 0
|
8月前
|
JavaScript 前端开发
说说你对事件循环的理解(event loop)
说说你对事件循环的理解(event loop)
|
8月前
|
JavaScript 前端开发
JS引擎的执行机制event loop
JS引擎的执行机制event loop
45 0
|
9月前
浅析Event Loop(事件循环)
浅析Event Loop(事件循环)
64 0
|
9月前
|
移动开发 前端开发 JavaScript
事件循环(Event Loop)
JavaScript 是一门单线程语言,这意味着它只有一个主线程来执行代码。这个主线程会按照代码的顺序执行任务,而且同一时间只能执行一个任务。