js任务分同步任务和异步任务。
在异步任务中,分为宏任务 macro 和微任务 micro。
通常微任务的执行是在当前主线程宏任务执行完毕之前,当前没有可执行的微任务时,才会开启下一个宏任务。
一、微任务
微任务包含 async、Promise.then、MutationObserver、webworker。
微任务不按照代码执行顺序执行,但他是在当前宏任务执行结束前都会执行完;可以获取回调。
nodeJs中的process.nextTick,也是微任务。
二、宏任务
宏任务包含 setTimeout、浏览器事件、新的script 标签、nodeJs事件(setImmediate、process.nextTick)、ajax、requestAnimationFrame、postMessage、MessageChannel。
宏任务不按照代码执行顺序执行,可以看成了拉了一个新的执行队列;没有回调。
三、event loop
event loop解决的问题是,实现一个异步队列,将比较耗时的任务放到异步队列,等主线程里的任务执行完再执行这个异步队列,执行完后将线程占用权转交给主线程。
触发异步队列的条件是,主线程执行完成后。
异步队列的执行顺序是,先进先出。
一、浏览器中的 event loop
一个script标签就是一个宏任务,所以宏任务队列是率先作为主线程的。在这个宏任务中如果有诸如 promise 、mutationObserver 之类的微任务,那么会将微任务延迟到主线程执行完执行。然后执行下一个宏任务。
二、nodeJs 中的 event loop
nodeJs 中的代码会先进过 v8 引擎编译,然后通过 nodeJs api 操作 libuv(基于 event loop 实现),将所有的任务交给一个工作线程。
当某一个工作完成时,会触发一个 callback,将线程控制权返还给应用程序。
libuv 的 event loop 分为几个阶段:
- 定时器阶段 timeout 等
- 循环迭代、io 回调
- 内部钩子函数
- poll 阶段,不断寻找新的事件循环或新的工作完成
- check 阶段,执行 setImmdiate
对于同一个线程内,先注册 setImmediate 后 注册 setTimout,为什么可能出现 setImmediate 比 setTimeout 先执行的情况?
因为可能在执行 setImmediate 时,setTImeout 还未被 libuv 捕获。
同样的,一堆 setTimeout 0 之前 写 setTimout 1 也可能出现这种情况。
libuv 的 poll 阶段有明显的“连续性”,如果在一个异步任务中同时创建 setImmediate 和 setTimout,那么 setImmediate 会先执行,这是因为 libuv 的异步阶段执行到了poll 阶段,setTImout 还没来的及到期,poll 阶段的下一个阶段 check 阶段就要执行 setImmediate 了。
setTimeout(() => {setTimeout(() => {console.log(1);});setImmediate(() => {console.log(0);}); }); setTimeout(() => {console.log(1); }); setImmediate(() => {console.log(0); }); // 1 0 0 1
nodeJs 中的微任务 process.nextTick,是优先级最高的微任务,会介于 event loop 的阶段切换之间执行。
v11以前的宏任务和微任务执行流程:
优先级相同(同一个线程内注册的)的宏任务(内的主线程代码)会都执行完,才执行它们对应的微任务。
v11以后的宏任务和微任务执行流程:
宏任务内创建的微任务,会在另一个宏任务执行前执行。已经和浏览器类似了。