示意图

简化版小故事
小明(Stack)是一名资深垃圾回收员,对于高效完成垃圾回收任务非常有心得,即使上海执行了垃圾分类,对于深爱着垃圾回收事业的小明来说也依然可以高效的完成。
周一的早上,阳光明媚,像往常一样小明来到了“JS Engine”社区,因为社区内小区(宏任务)众多,道路也比较复杂,所以小明一直都依赖于自己的掌上机器人EventLoop来帮助自己以最优的方式完成垃圾回收,刚进入社区,小明就收到了EventLoop的提示信息,系统已经根据监控系统为小明安排好了第一个前往的小区(MacroTask,JS文件),小明火速奔赴小区,从小区门口处开始一个接着一个垃圾桶开始回收,突然小明发现了一堆建筑垃圾(setTimeout等),因为建筑垃圾实在太多了,还要等着建筑工人(Webapis)打包很浪费时间,于是小明就告诉建工人说等你打包完了给控制中心打个电话,他们会安排到我到我计划(任务队列)中的,于是小明继续往后走,走着走着小明又发现了一堆湿垃圾还没有完成沥水(Promise等),聪明的小明看了下时间,觉得等不了多久,于是就手动把这件事记到了自己的小本子上(微任务队列),终于小明完成了整个小区所有垃圾的会收任务,这时小明拿起了自己的智能终端,计划显示后面又有三个小区可以去回收了,但是小明很疑惑,EventLoop并没有安排小明马上去下一个小区,而是弹出了一条提示信息,小明打开信息后才恍然大悟,原来自己还有几个沥水的垃圾桶(微任务)没有完成回收,于是乎小明火速回到小区把所有沥水的湿垃圾都回收了回去,这时EventLoop才提示让小明去下一个小区,同时小明也看到刚才的建筑垃圾也已经准备好并进入了小明的计划,排在第四个,于是小明就跟着智能终端的提示一步一步的完成了所有垃圾的回收,等小明完成最后一个小区的时候看了下距离下班还有一个小时,小明忍不住感慨,虽然只有我一个人(单线程),但是多亏有了EventLoop的合理规划,我依然可以提前下班,哈哈。。。
标准图

详解
以标准图为例,JS引擎会首先为Code块包在一个函数中并作为事件start或者launch的回调函数,然后JS引擎会emit start或者launch事件,该事件就会作为宏任务被添加到任务队列(任务队列中的都是宏任务),然后JS引擎会从任务队列中获取第一个宏任务并执行,即开始执行console.log(1).
当执行到setTimeout时,JS引擎会将回调函数放入宏任务队列。
继续往下执行到promise时,JS引擎会为function(){console.log(3);}创建微任务并放入微任务队列。
最后执行console.log(4), 这一行代码执行完之后整个代码块也就执行完了,EventLoop会查看Stack和任务队列,如果Stack为空会取出第一个任务到Stack去执行,但是在此之前会先清空微任务队列。
于是下面就打印出了console.log(3);
在清空微任务队列之后,EventLoop会取出任务队列中的第一个宏任务即setTimeout的会调函数执行并打印出4。
关键点
JS引擎有栈和堆得概念,对于宏任务和微任务我们主要来聊聊栈,由于JS是单线程,所以当前执行的任务都会出现在栈中。
之前看到网上很多说法是说JS引擎会先执行所有的微任务,再去执行宏任务,其实严格来说应该是先执行宏任务,之后会在执行下一条宏任务之前清空微任务队列。
宏任务:整体代码Script、setTimeout、setInterval、setImmediate...
微任务:Promise、process.nextTick...
EventLoop:负责监控调度任务队列以及工作栈。
Draft Version