一、什么是事件循环?
通俗来说就是我们所编写的Javascript和浏览器或者Node之间的一个桥梁。
浏览器的事件循环是我们编写的Javascript代码和浏览器API调用(setTimeout/AJAX/监听事件)的一个桥梁,桥梁之间他们通过回调函数进行通信。
Node的事件循环是一个我们编写的JavaScript代码的系统调用(file system/network等)之间的一个桥梁,桥梁之间他们通过回调函数进行通信。
连接两个东西之间的桥梁。
二、进程与线程
进程和线程是操作系统中的两个概念:
- 进程(process):计算机已经运行的程序
- 线程(thread):操作系统能够运行运算调度的最小单元
听起来很抽象,直观一点:
- 进程:启动一个应用程序,就会默认启动一个进程(也有可能是多个)
- 线程:每一个进程中,都会启动一个线程用来执行程序中的代码,这个线程被称为主线程;
所以我们也可以说,进程是线程的容器
抽象表达:
工厂 => 车间 => 工人(多人)
操作系统 => 进程 => 线程
三、Node架构分析
浏览器中的EventLoop是根据HTML5定义的规范来实现的,不同的浏览器可能会有不同的实现,而Node中是由libuv实现的。
我们来看在很早就给大家展示的Node架构图:
- 我们会发现libuv中主要维护了一个EventLoop和worker threads(线程池);
- EventLoop负责调用系统的一些其他操作:文件的IO、Network、child-processes等
libuv是一个多平台的专注于异步IO的库,它最初是为Node开发的,但是现在也被使用到Luvit、Julia、pyuv等其
他地方;
阶段概述
- 定时器:本阶段执行已经被 setTimeout() 和 setInterval() 的调度回调函数。
- 待定回调:执行延迟到下一个循环迭代的 I/O 回调。
- idle, prepare:仅系统内部使用。
- 轮询:检索新的 I/O 事件;执行与 I/O 相关的回调(几乎所有情况下,除了关闭的回调函数,那些由计时器和 setImmediate() 调度的之外),其余情况 node 将在适当的时候在此阻塞。
- 检测:setImmediate() 回调函数在这里执行。
- 关闭的回调函数:一些关闭的回调函数,如:socket.on(‘close’, …)。
Node的事件循环队列:
- ticks队列
- 其他微任务队列
- timers队列:setTimeout
- I/O队列
- setImmediate 队列
- close队列
面试题:
async function async1() {
console.log('async1 start');
await async2()
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(() => {
console.log('setTimeout0');
}, 0);
setTimeout(() => {
console.log('setTimeout2');
}, 300);
setImmediate(() => console.log('setImmediate'))
process.nextTick(() => console.log('nextTick1'))
async1()
process.nextTick(() => console.log('nextTick2'))
new Promise((resolve) => {
console.log('promise1');
resolve()
console.log('promise2');
}).then(() => {
console.log('promise3');
})
console.log('script end');
解析:
结果:
/**
* script start
* async1 start
* async2
* promise1
* promise2
* script end
* nextTick1
* nextTick2
* async1 end
* promise3
* setTimeout0
* setImmediate
* setTimeout2
*/