一、单线程
JS是单线程,为了不阻塞线程,JS通过事件循环
的方案解决耗时任务。
二、任务队列(同步/异步)
1.任务
可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。
同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
2.任务队列的类型
任务队列存在两种类型,一种为microtask queue,另一种为macrotask queue。
上图所列出的任务队列均为macrotask queue,而ES6 的 promise[ECMAScript标准]产生的任务队列为microtask queue。
microtask queue:唯一,整个事件循环当中,仅存在一个;执行为同步,同一个事件循环中的microtask会按队列顺序,串行执行完毕;
macrotask queue:不唯一,存在一定的优先级(用户I/O部分优先级更高);异步执行,同一事件循环中,只执行一个。
3.完整的事件循环流程
将microtask加入到JS运行机制流程中,则:
step1:主线程读取JS代码,此时为同步环境,形成相应的堆和执行栈;
step2: 主线程遇到异步任务,指给对应的异步进程进行处理(WEB API);
step3: 异步进程处理完毕(Ajax返回、DOM事件处罚、Timer到等),将相应的异步任务推入任务队列;
step4:主线程查询任务队列,执行microtask queue,将其按序执行,全部执行完毕;
step5:主线程查询任务队列,执行macrotask queue,取队首任务执行,执行完毕;
step6:重复step4、step5。
microtask queue中的所有callback处在同一个事件循环中,而macrotask queue中的callback有自己的事件循环。
简而言之:同步环境执行 -> 事件循环1(microtask queue的All)-> 事件循环2(macrotask queue中的一个) -> 事件循环1(microtask queue的All)-> 事件循环2(macrotask queue中的一个)...
利用microtask queue可以形成一个同步执行的环境,但如果Microtask queue太长,将导致Macrotask任务长时间执行不了,最终导致用户I/O无响应等,所以使用需慎重
三、事件循环(js执行机制)
事件循环是js实现异步的一种方法,也是js的执行机制。
事件循环(Event Loop):
(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。
task主要包含:setTimeout
、setInterval
、setImmediate
、I/O
、UI交互事件
microtask主要包含:Promise
、process.nextTick
、MutaionObserver
四、Node.js的Event Loop
(1)V8引擎解析JavaScript脚本。
(2)解析后的代码,调用Node API。
(3)libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎。
(4)V8引擎再将结果返回给用户。
除了setTimeout和setInterval这两个方法,Node.js还提供了另外两个与"任务队列"有关的方法:process.nextTick和setImmediate。它们可以帮助我们加深对"任务队列"的理解。
process.nextTick方法可以在当前"执行栈"的尾部----下一次Event Loop(主线程读取"任务队列")之前----触发回调函数。也就是说,它指定的任务总是发生在所有异步任务之前。setImmediate方法则是在当前"任务队列"的尾部添加事件,也就是说,它指定的任务总是在下一次Event Loop时执行,这与setTimeout(fn, 0)很像
Node
的Event Loop分阶段,阶段有先后,依次是
- expired timers and intervals,即到期的setTimeout/setInterval
- I/O events,包含文件,网络等等
- immediates,通过setImmediate注册的函数
- close handlers,close事件的回调,比如TCP连接断开
同步任务及每个阶段之后都会清空microtask队列
- 优先清空next tick queue,即通过
process.nextTick
注册的函数 - 再清空other queue,常见的如Promise
而和规范的区别,在于node会清空当前所处阶段的队列,即执行所有task
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------