原地址: https://zhuanlan.zhihu.com/p/87684858
关于执行中的线程:
主线程:也就是 js 引擎执行的线程,这个线程只有一个,页面渲染、函数处理都在这个主线程上执行。
工作线程:也称幕后线程,这个线程可能存在于浏览器或js引擎内,与主线程是分开的,处理文件读取、网络请求等异步事件。
任务队列( Event Queue )
所有的任务可以分为同步任务和异步任务,同步任务,顾名思义,就是立即执行的任务,同步任务一般会直接进入到主线程中执行;而异步任务,就是异步执行的任务,比如ajax网络请求,setTimeout 定时函数等都属于异步任务,异步任务会通过任务队列的机制(先进先出的机制)来进行协调。具体的可以用下面的图来大致说明一下:
同步和异步任务分别进入不同的执行环境,同步的进入主线程,即主执行栈,异步的进入任务队列。主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。 上述过程的不断重复就是我们说的 Event Loop (事件循环)。
在事件循环中,每进行一次循环操作称为tick,通过阅读规范可知,每一次 tick 的任务处理模型是比较复杂的,其关键的步骤可以总结如下:
1.在此次 tick 中选择最先进入队列的任务( oldest task ),如果有则执行(一次)
2.检查是否存在 Microtasks ,如果存在则不停地执行,直至清空Microtask Queue
3.更新 render
4.主线程重复执行上述步骤
可以用一张图来说明下流程:
宏任务主要包含:script( 整体代码)、setTimeout、setInterval、I/O、UI 交互事件、setImmediate(Node.js 环境)
微任务主要包含:Promise、MutaionObserver、process.nextTick(Node.js 环境)
setTimeout/Promise 等API便是任务源,而进入任务队列的是由他们指定的具体执行任务。来自不同任务源的任务会进入到不同的任务队列。其中 setTimeout 与 setInterval 是同源的。
举例
console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0); Promise.resolve().then(function() { console.log('promise1'); }).then(function() { console.log('promise2'); }); console.log('script end');
整体 script 作为第一个宏任务进入主线程,遇到 console.log,输出 script start
遇到 setTimeout,其回调函数被分发到宏任务 Event Queue 中
遇到 Promise,其 then函数被分到到微任务 Event Queue 中,记为 then1,之后又遇到了 then 函数,将其分到微任务 Event Queue 中,记为 then2
遇到 console.log,输出 script end
至此,Event Queue 中存在三个任务:宏任务:setTimeout 微任务:then1、then2
执行微任务,首先执行then1,输出 promise1, 然后执行 then2,输出 promise2,这样就清空了所有微任务
执行 setTimeout 任务,输出 setTimeout 至此,输出的顺序是:script start, script end, promise1, promise2, setTimeout
由上面可理解:
执行第一个宏任务: 整体代码 输出了script start, script end
根据上图
执行完宏任务需要执行所有微任务, 所以then先执行输出promise1, promise2, , 最后再进行宏任务setTimeout