Event loop即事件循环,就是指浏览器或者Node(JS运行的环境)用来解决JS单线程运行阻塞的问题的一种机制。 关于Event loop,它分为MacroTask(宏任务)和MicroTask(微任务)。
首先,我们来谈一下JS的单线程运行。
何为单线程?
当一个程序运行时,就可以视为一个进程。运行中的程序和程序所需要的资源都是进程,而一个进程一般是由多个线程组成的。
多个线程意味着有多个不同的执行流执行不同的任务。多线程运行时,不是其他线程等待一个执行完,而是各自运行。但是,同时多线程运行时占用的内存偏多,且会出现共享资源争夺的情况,容易造成很多麻烦的Bug。
而单线程是按顺序执行,不需要占用过多的内存资源。
那么为何JS会是单线程呢?
JS从诞生之初就是单线程。阮一峰老师在他的博客中提到:大概是因为不想让浏览器变得像多线程那样复杂,因为多线程需要共享资源、且有可能修改彼此的运行结果,对于一个网络脚本语言来说,太过于复杂了,还是单线程比较适合JS。而Event loop就是用来解决JS单线程运行阻塞、等待时间过长的一种机制。
宏任务(Macro Task)
宏任务是由宿主,也就是JS运行的环境(浏览器和Node),发起的。
script代码、setTimeout、setInterval、setImmediate、I/O(Node.js)、UI rending/UI事件都是宏任务的具体事件
在代码中,一般后运行,并会触发新一轮的Tick。
微任务(Micro Task)
微任务是由JS引擎发起的。
Promise、MutationObserve、Object.observe(已废弃;Proxy对象替代)、Process.nextTick(Node.js独有)
在代码中,一般会先行运行,并不会触发新一轮的Tick。
浏览器与Event loop
程序中,会设置两个线程,一个是负责程序本身的运行,为“主线程”(main thread);另一个则负责主线程与I/O操作之间的通信,即调用栈(执行栈)call-stack,所有的任务都会放在栈中等待被主线程执行。
在主线程中,最先执行的任务会存储在任务队列中放入执行栈中执行,当任务队列为空时,则会从微任务队列中选择任务放入到任务队列中,直至任务都被完成。执行进入Micro的检查点时,会先设置Micro的检查点为true,当Micro不为空时,选择一个任务task进入队列中,并将其设置为已选择的microtask,运行microtask,将已经执行完的microtask设置为null,清理indexDB事务,重新设置Micro检查点为false,更新界面。
浏览器中Event loop会先检查执行栈,如果执行栈为空,则去执行宏任务,宏任务执行完成之后,会去检查微任务队列,如果不为空,则依次执行微任务,直至微任务执行完成之后再去执行宏任务。
Node.js与Event loop
Node中Event loop中的libuv实现的。
什么是libuv?
libuv是一个高性能、事件驱动的异步I/O库。libuv封装了不同平台底层对于**异步I/O模型**的实现。\
现阶段的Node提供libuv作为封装层,使Node具备了跨平台的能力。
Node的Event loop被分为6个阶段:
- timers:执行setTimeout和setInterval中时间到期满足(达到最快定时器的阈值 - 由于调度会产生一些延时)的callback。
- pending callback:某些系统操作的回调(例如:TCP错误类型)
- poll:
1. 执行I/O回调
2. 处理轮询队列中的事件
如果poll队列不为空,则会循环遍历执行它们的callback队列;如果有setImmediate()需要回调执行,则停止poll阶段进入check阶段执行回调;如果没有setImmediate(),poll将callback放入队列中执行;如果poll为空,则会判断timer是否超时,如果有的话则回到timer。
- check:如果poll已完成或者闲置并且setImmediate()已排队,则立即执行check阶段。
- close callback:执行close事件中的callback。
setImmediate()与定时器
setImmediate()在poll阶段执行完成或者闲置之后立即执行,在check阶段\
定时器在timer阶段执行
Process.nectTick()
process.nextTick()从技术上讲,并不是事件循环的一部分。在每个阶段完成之后,如果存在nextTick就会清空队列中所有的callback立即执行nextTick。
浏览器的Event loop与Node中的loop的区别
Node端的事件循环,MicroTask在事件循环的各个阶段之间执行
浏览器中的时间循环,MicroTask在事件循环的MacroTask执行完之后再执行