1.1 概括
这篇文章主要讲解下前端javascript和node中的事件循环机制,原因如下:
- 大家都知道,浏览器的渲染引擎主要有ui线程和js线程两部分,而由于js的执行可能会休改ui线程的渲染。所以二者是互斥的。即当ui线程在执行时,会阻塞js线程的运行。反之亦然。
- js的主线程是单线程的,这是为了不会产生多个js同时对DOM进行修改而导致的页面混乱。但为什么说js的主线程是单线程呢。因为js为了提升性能。js又供了事件回调、定时任务、ajax请求等用于跳出主线程任务。
1.2 堆、栈、队列
在讲解事件环机制前,先和大家明确几个基础概念
- 堆(heap):存放js中的对象,向外暴露其内存地址。不会自动清除(其引用为0时,清除)
- 栈(stack):先进后出的数据模式,js的主调用流程的存储结构,调用结束即清除
- 队列(queue):先进先出的存储模式,js中宏任务(macro-task)及微任务(micro-task)的存储结构
1.3 宏任务和微任务
- 宏任务(micro-task)主要是:setTimeout、setInterval、Promise的构造函数是同步的、setImmediate、I/O、UIrendering
- 微任务(macro-task)主要是:Promise的回调(then)、process.nextTick
2.1 javascript中的事件环
- js文本在进行解析后,会将文件中的任务进行分配为:主线程队列,微任务队列和宏任务队列
- 主线程队列会依次从队列中pop到调用栈中执行,在执行中如果内部包含微任务/宏任务则会再次推入微任务队列/宏任务队列
- 主任务队列执行完毕后,会查看微任务队列&宏任务队列中是否有需要执行的任务,拥有的话,将其推入到主任务队列。
- 循环上述操作,形成js的执行环
2.2 客户端js时间环特点
js在调用栈中执行宏任务时,如果内部含有微任务,则这个微任务不会被推入微任务队列中,直接在宏任务执行时直接执行
2.3 node中的事件环
- 写的JavaScript脚本会交给V8引擎解析
- 解析后的代码,调用Node API,Node会交给Libuv库处理
- Libuv库将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎
- V8引擎再将结果返回给用户
node的宏任务中也拥有自己的先后顺序 :如下图
上图解析:
- 根据上图可知,node中的宏任务也是分为几个模块的,如timers,poll,check等
- 主队列任务执行时,如果内部有相应宏任务,则会根据宏任务类型放置到不同模块中。当主任务队列执行完毕后,宏任务列表会依据模块顺序(从上到下)将可立即执行的宏任务推至主任务队列中,依次执行