1.从js单线程谈起
众所周知,javascript诞生之初的目的是为了在浏览器上实现用户的交互,以及操作DOM。因此js必须是单线程的,否则它将带来复杂的同步问题。所以单线程是js这一门语言的核心特性。
2.任务队列
但是如果只有单线程的话就会导致线程堵塞,如果前一个任务一直执行没有结束, 它就会一直占用线程,导致后面的任务无法继续执行。例如一些IO操作和Ajax等 网络操作需要等待很长的时间才能返回数据,在等待的过程中cpu的大多数时间并 没有进行运算,而是在以空闲的状态等待数据返回。这就导致cpu资源的大量浪费。 js的作者也意识到了这一点,于是产生了异步任务(asynchronous)和同步任务 (synchronous)的概念。js的运行机制如下:
1.所有同步任务都在主线程上执行,形成一个执行栈
2.除了主线程之外还有一个“任务队列”,异步任务执行完之后会,
会在任务队列中添加一个事件
3.等主线程执行栈中的任务执行完之后,系统再去读取“任务队列”
中的事件,此时异步任务进入执行栈执行。
4.主线程会不断重复上述三个过程

3.事件循环(Event Loop)
上述主线程从“任务队列”中读取事件,这个不断循环的过程就叫事件循环(Event Loop)。 在事件循环的过程中每一次的循环过程称为Tick。而每次Tick的过程则是本文所要讨论的重点。 在这里先说两个概念:宏任务(Macro Task)和微任务(Micro Task)
1.宏任务(Macro Task)主要包含:script( 整体代码)、setTimeout、
setInterval、I/O、UI 交互事件、setImmediate(Node.js 环境)
2.微任务主要包含:Promise、MutaionObserver、process.nextTick(Node环境)

tick详细过程如下:
1.执行栈先执行宏任务的代码,在执行过程中若有宏任务则将宏任务添加到宏任务队列,
若出现微任务则将微任务添加到微任务队列。
2.等宏任务执行完毕后,执行栈开始执行微任务,执行过程中若有宏任务则将其添加到
宏任务队列,若出现微任务则将其添加到微任务队列。
3.当前微任务执行完毕后,检查微任务队列是否有其它微任务,如果存在其他微任务则
继续执行其他微任务直到微任务队列清空。
4.本次tick结束,进入下次循环重复上述三个步骤。
4.例子
console.log('script start')
setTimeout(() => {
console.log('setTimeout')
}, 0)
Promise.resolve().then(() => {
console.log('promise1')
}).then(() => {
console.log('promise2')
})
console.log('script end')
运行结果
scriptstart scriptend promise1 promise2 setTimeout
运行过程
1.开始执行第一个宏任务Run script:
宏任务栈(Macrotasks): Run script
微任务栈(Microtasks):
执行栈(JS stack): script
输出(log): scriptstart
2.添加steTimeout callback到宏任务栈:
Macrotasks: Runscript,setTimeoutCallback
Microtasks:
JS stack: script
log: scriptstart
3.添加Promise then到微任务栈:
Macrotasks:Run script,setTimeouCallback
Microtasks:Promise then
JS stack:script
log: scriptstart,scriptend
4.第一个宏任务执行完毕,执行栈清空:
Macrotasks:Run script,setTimeouCallback
Microtasks:Promise then
JS stack:
log:scriptstart,scriptend
5.开始执行微任务,执行过程中遇见第二个微任务Promise then
将其添加到微任务栈:
Macrotasks:Run script, setTimeoutCallback
Microtasks:Promise then, Promise then
JS stack:PromiseCallback
log:scriptstart,scriptend,promise1
6.第一个微任务执行完毕,检测微任务栈是否清空,
若未清空继续执行:
Macrotasks:Runscript,setTimeoutCallback
Microtasks:Promise then
JS stack:PromiseCallback
log: scriptstart,scriptend,promise1,promise2
7.微任务栈清空,本次事件循环结束,进入下次事件循环,
开始执行宏任务setTimeouCallback:
Macrotasks:setTimeoutCallback
Microtasks:
JS stack:setTimeouCallback
log:scriptstart,scriptend,promise1,promise2,setTimeout
总结
1.由上述过程可以看出js是优先处理微任务的,而定义的宏任务则是会在下一次Tick才执行。所以建议优先级较高的代码使用 微任务。
2.js是一门单线程语言所有的异步事件都要放入事件队列里,等待执行栈安装事件循环来读取执行。