一、事件循环
事实上,事件循环可以理解成我们编写的js和浏览器或者Node之间的一个桥梁
浏览器的事件循环是一个我们编写的Js代码和浏览器API调用(setTimeout/AJAX/监听事件等)的一个桥梁
桥梁之间他们通过回调函数进行沟通
Node的事件循环是一个我们编写的js代码和系统调用(file system /netWork等 )之间的一个桥梁
桥梁之间他们通过回调函数进行沟通
二、进程和线程
线程和进程是操作系统中的两个概念:
进程(process):计算机已经运行的程序
线程(thread):操作系统能够运行调度的最小单位
直观解释:
进程:我们可以认为,启动一个应用程序,就会默认启动一个进程(也可能是多个进程)
线程:每一个进程中,都会启动一个线程用来执行程序中的代码,这个线程被称之为主线程
所以我们也可以说进程是线程的容器
例子解释:操作系统类似于一个工厂,工厂中里有很多车间,这个车间就是进程,每个车间可能有一个以上的工人在工厂,这个工厂就是线程
三、多进程多线程开发
cpu的运行速度非常快,它可以快速的在多个进程之间迅速的切换,当我们的进程中的线程获取到时间片时,就可以快速执行我们编写的代码,对于用户来说是感受不到这种快速的切换的
但是js的代码执行是在一个单独的进程中进行的,不能进行耗时的操作,不然会进行阻塞
那么传入的一个定时器函数,会在什么时候被执行呢?
事实上,setTimeout是调用了web api ,在合适的时机,会将timer函数加入到一个时间队列中,
事件队列中的函数,会被放入到调用栈中,在调用栈中被执行
四、浏览器的事件循环 
每一个回调函数就是一个任务(macrotask 宏任务 macrotask queue 宏任务队列 )
Promise().then((data)=>{}) 里面的data就是微任务(microtask) microqueue微任务队列
五、宏任务和微任务
\
但是事件循环中并非只维护这一个队列,事实上是有两个队列:
宏任务队列(macrotask queue):ajax、setTimeout、setInterval、DOM监听等
微任务队列 (microtask queue) :Pormise的then回调、Mutation Observer Api等
浏览器里面优先执行微任务队列(将微任务队列全部执行完之后),再执行宏任务队列(在执行任何一个宏任务之前,都会优先查看微任务队列是否有任务需要执行,也就是宏任务执行之前,必须保证微任务队列是空的,如果不为空,则优先执行微任务队列中的任务)
注意:在promise函数里面包裹的函数会被当作main script直接执行 不用放进到微任务队列里面,即await关键字后的函数
(
async、await是promise的一个语法塘
(1)我们可以将await关键字后面执行的代码,看作是包裹在(resolve、reject)=>(函数执行)中的代码;
(2)await的下一个语句,可以看作是then(res=>(函数执行))中的代码
)
六、Node的架构分析
浏览器中的EventLoop是根据H5定义的规范来实现的,不同的浏览器可能会有不同的实现,而Node是由libuv实现的。
libuv是一个多平台的专注于异步IO的库,它最初是为Node开发的,最主要是为了维护EVENT Loop
七、阻塞IO和非阻塞IO
如果我们希望在程序中对一个文件进行操作,那么我们就需要打开这个文件:通过文件描述符
事实上对文件的操作,是一个操作系统的系统调用(IO系统)
操作系统通常为我们提供了两种调用方式:阻塞式调用和非阻塞式调用
阻塞式调用:调用结果返回之前,当前线程处于阻塞态(阻塞态CPU是不会分配时间片的),调用线程只有在得到调用结果之后才会继续执行
非阻塞式调用:调用执行之后,当前线程不会停止执行,只需要过一段时间来检查一个有没有结果返回即可
所以我们开发中的很多耗时操作,都是可以基于这样的 非阻塞式调用:
比如网络请求本身使用Socket通信,而Socket本身提供了select模型,也可以进行非阻塞方式的工作
比如文件读写的IO读写,我们可以使用操作系统提供的基于时间的回调机制
非阻塞IO的问题:我们并没有获取到需要读取(我们以读取为例)的结果
那么就意味着为了可以知道是否读取到了完整的数据,我们需要频繁的去确定读取到的数据是否是完整的
这个过程我们称之为轮训操作
libuv提供了一个线程池(Thread Pool)
线程池会负责所有相关的操作,并且会通过轮训或者其他的方式等待结果;
当获取到结果时,就可以将对应的回调放到事件循环(某一个事件队列)中;
事件循环就可以负责接管后续的回调工作,告知JS应用程序执行的回调函数;
阻塞和非阻塞,同步和异步的区别?
阻塞和非阻塞是对于被调用者来说的,在我们这里就是系统调用,操作系统为我们提供了阻塞调用和非阻塞调用
同步和异步是对于调用者来说的,在我们这里就是自动的程序
八、Node事件循环的阶段
但是一次完整的事件循环Tick分成很多个阶段:
定时器(Timers):本阶段执行已经被setTimeout()和setInterval()的调度回调函数
待定回调(Pending Callback):对某些操作系统(如TCP错误类型)执行回调,比如TCP连接时接收到Econnerfused
idle,prepare:仅系统内部使用
轮询(Poll) 检索新的I/O事件;执行与I/O相关的回调
检测(check)SetImmediate()回调函数在这里执行
关闭的回调函数:一些关闭的回调函数,如:socket.on('close,...)
九、Node的微任务和宏任务
我们会发现从一次事件循环的Tick来说,Node的事件循环更复杂,它也分为微任务和宏任务
Timeout、setInterval、IO事件、setImmediate、close事件;
微任务(microtask):Promise的then回调、process.nextTick、queueMicrotask;