js是单线程
在讨论js执行机制之前,首先我们得明白:js是单线程执行的,这是js语言的一大特性,就是说,在一段时间内只能做一件事情。

js为什么是单线程的呢
首先我们要知道,js是用于和浏览器打交道的脚本语言,主要用途是与用户交互,操作dom节点,如果是同步的话,会有很严重的同步问题。比如,js有两个线程,一个修改了某个dom节点,另一个删除了这个dom节点,此时该以哪个为准呢?

Event Loop是什么?
为了解决同步和异步问题,浏览器提供了一个事件队列 Event Queue。Event Loop是一个执行模型,在不同的地方有不同的实现。

任务队列
此时,任务队列的概念就出来了,单线程就意味着所有的任务都需要排队,等前一个任务结束,后一个才能开始执行。如果前一个任务很长,那么后一个就要一直等着(所以,setTimeout定时器可能不是准时的哦)。
所有的任务都可分为同步任务和异步任务,同步任务是指在主线程上排队执行的任务,只有等前一个任务执行完才能执行后一个任务。异步任务是指,不进入主线程,而是进入任务队列排队的任务,只有当主线程中的同步任务执行完毕,通知任务队列,可以开始执行了,就以先进先出的顺序开始执行异步任务。

宏任务和微任务是什么?
宏任务:也叫macrotask,在JavaScript中,一些异步任务会进入macrotask队列中,等待被调用,常见的异步任务有:setTimeout,setInterval,dom事件回调函数,Ajax回调函数等。
微任务:也叫microtask,常见的有Promise,process.nextTick 。

了解JavaScript代码执行的具体流程:
1、执行全局script同步代码
2、全局script同步代码执行完之后,调用栈会清空
3、从微队列中取出位于队首的回调任务,放入调用栈执行
4、执行完后,再取出微队列中位于队首的回调任务,放入调用栈执行,如果在执行回调任务过程中又出现了微任务,则将其放到微队列末尾,循环直到将微任务执行完。
5、微任务执行完后,调用栈清空,微队列也为空,此时从宏队列中取出队首的回调任务,放入调用栈执行
6、若在执行宏任务过程中出现微任务,则先执行微任务,直到微队列为空再执行宏任务,循环此过程

总的来说执行顺序是:主线程代码 (同步代码)> 微任务 > 宏任务

举个例子,以下代码会依次输出什么呢?

console.log(1)
setTimeout(() => console.log('a'));
Promise.resolve().then(
   () => console.log('b')
 ).then(
   () => Promise.resolve('c').then(
     (data) => {
       setTimeout(() => console.log('d'));
       console.log('f');
       return data;
     }
   )
 ).then(data => console.log(data));

答案是:1,b,f,c,a,d
1、首先执行全局script同步代码,即打印数字1,同时会将微任务依次放入微队列,即将Promise放入微队列,宏任务依次放入宏队列,将setTimeOut放入宏队列,等待被调用执行。
2、然后执行微队列的队首任务,即Promise回调函数,打印b。
3、再往下执行碰到setTimeOut,将其放入宏队列末尾等待执行,
继续往下打印f,c,至此微任务已执行完,调用栈为空。
4、开始执行宏队列队首的回调任务,即第一个setTimeOut,回调任务里没有包含微任务,即依次打印a,d,至此程序执行完。