概念同步任务(Synchronous)在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务异步任务(Asynchronous)不进入主线程,而是进入“任务队列”的任务,只有主线执行栈清空,异步任务才进入主线执行栈执行任务队列(Task Queue)包含有异步任务的队列,包括“宏任务”与“微任务”宏任务(Macrotasks / Task)创建文档对象、解析 HTML、执行主线程代码(script)执行各种事件:页面加载、输入、点击setTimout,setInterval,setImmediateI/O,Ajax,UI rendering微任务(Microtasks / Jobs)process.nextTickPromise.thenObject.observe(已废弃)MutationObserver事件循环(Event Loop)浏览器下的事件循环事件循环是js实现异步的一种方法,也是js的执行机制JavaScript 主线程会在执行栈清空后,读取任务队列,入栈第一个宏任务,主线程执行完该任务后又会先检查微任务队列并完成里面的所有微任务,包括新创建的微任务,完成一次事件循环。之后再次去读取任务队列,不断循环注意:每次循环只会入栈一个宏任务,所以多个宏任务需要多次事件循环才能执行完每次循环会执行所有的微任务,所以每次循环结束后微任务队列被清空Node.JS下的事件循环timers:执行 setTimeout 和 setInterval 中到期的 callbackI/O callbacks:除了以下操作的回调函数,其他的回调函数都在这个阶段执行。setTimeout,setInterval,setImmediate 的 callback用于执行 close 事件(关闭请求)的 callback,例如 socket.on(‘close’, callback)idle, prepare:libuv 内部调用poll:最为重要的阶段,用于等待还未返回的 I/O 事件,比如服务器的回应、用户移动鼠标等等这个阶段的时间会比较长。如果没有其他异步任务要处理(比如到期的定时器),会一直停留在这个阶段,等待 I/O 请求返回结果。check:执行 setImmediate 的 callbackclose callbacks:执行 close 事件(关闭请求)的 callback,例如 socket.on(‘close’, callback)事件循环的每一次循环都需要依次经过上述的阶段。 每个阶段都有自己的 callback 队列,每当进入某个阶段,都会从所属的队列中取出callback来执行,当队列为空或者被执行callback的数量达到系统的最大数量时,进入下一阶段。这六个阶段都执行完毕称为一轮循环注意:不同于浏览器的是,在每个阶段完成后,microTask队列就会被执行,而不是MacroTask任务完成后。每个阶段完成后,微任务队列就会被执行。如果在timers阶段执行时创建了setImmediate则会在此轮循环的check阶段执行,如果在timers阶段创建了 setTimeout,由于timers已取出完毕,则会进入下轮循环,check阶段创建timers任务同理递归的调用 process.nextTick 会导致 I/O starving,官方推荐使用 setImmediateNode.JS与浏览器下的差异浏览器环境下,microtask 的任务队列是每个 macrotask 执行完之后执行Node.js中,microtask 会在事件循环的各个阶段之间执行,也就是一个阶段执行完毕,就会去执行 microtask 队列的任务setTimeout(()=>{ console.log(’timer1’) Promise.resolve().then(function() { console.log(‘promise1’) })}, 0)setTimeout(()=>{ console.log(’timer2’) Promise.resolve().then(function() { console.log(‘promise2’) })}, 0)// 浏览器输出:// time1// promise1// time2// promise2// Node输出:// time1// time2// promise1// promise2定时器setTimeout(callback, time)经过指定时间后,把要执行的任务 callback 加入到任务队列中因为JS是单线程,任务要一个一个执行,如果前面的任务需要的时间太久,那么只能等着,导致真正的延迟时间可能远远大于指定时间(time ms)setTimeout(callback, 0)指定某个任务 callback 在主线程最早可得的空闲时间执行,意思就是不用再等多少秒了,只要主线程执行栈内的同步任务全部执行完成,栈为空就马上执行0ms 实际上是不可能的,在浏览器中 setTimeout() / setInterval() 的每调用一次定时器的最小间隔 >=4ms,这通常是由于函数嵌套导致(嵌套层级达到一定深度),或者是由于已经执行的 setInterval 的回调函数阻塞导致的在 Node.JS 环境为 1ms,但也取决于系统当时的状况setTimeout(function () { console.log(“1”);}, 0)console.log(2)// 输出 2 1setInterval(callback, time)每过指定时间(time ms),会有 callback 进入任务队列。若 callback 执行时间超过了指定时间,那么就会导致 callback 连续执行,完全看不出来有时间间隔了setImmediate(callback)Node.JS 特有定时器,在事件循环的 check 阶段执行process.nextTick(callback)Node.JS 特有定时器,在事件循环各个阶段结束后执行从技术上讲,它不是事件循环的一部分同循环下 process.nextTick 会优于 Promise.thenPromise.resolve().then(() => console.log(1));process.nextTick(() => console.log(2));// 输出 2 1注意连续的 setTimeout,setImmediate 在再 timer 阶段的执行顺序是不确定的,取决于系统当时的状况但是把 setTimeout,setImmediate 放到一个 I/O 回调里面,就一定是 setImmediate 先执行,因为 poll 阶段后面就是 check 阶段setImmediate(() => { console.log(’timer1’) Promise.resolve().then(function () { console.log(‘promise1’) })})setTimeout(() => { console.log(’timer2’) Promise.resolve().then(function () { console.log(‘promise2’) })}, 0)// Node输出:// timer1 timer2// promise1 或者 promise2// timer2 timer1// promise2 promise1fs.readFile(’test.js’, () => { setTimeout(() => console.log(1)); setImmediate(() => console.log(2));})// 输出 2 1// 先进入 I/O callbacks 阶段,然后是 check 阶段,最后才是 下一次事件循环的 timers 阶段。因此,setImmediate 才会早于setTimeout 执行。示例console.log(0)new Promise(function(resolve) { console.log(1); resolve();}).then(function() { console.log(2)})setTimeout(function() { console.log(3); new Promise(function(resolve) { console.log(4); resolve(); }).then(function() { console.log(5) })})new Promise(function(resolve) { console.log(6); resolve();}).then(function() { console.log(7)})setTimeout(function() { console.log(8); new Promise(function(resolve) { console.log(9); resolve(); }).then(function() { console.log(10) })})console.log(11)浏览器环境第一轮事件循环第一轮事件循环宏任务开始执行代码,遇到 console.log 输出 0接着遇到 new Promise 其回调函数作为同步任务直接执行遇到 console.log 输出 1遇到 then 回调函数作为异步任务进入“微任务队列”接着遇到 setTimeout 其回调函数作为异步任务进入“宏任务队列”接着遇到 new Promise 其回调函数作为同步任务直接执行遇到 console.log 输出 6遇到 then 回调函数作为异步任务进入“微任务队列”接着遇到 setTimeout 其回调函数作为异步任务进入“宏任务队列”最后遇到 console.log 输出 11此时第一轮事件循环宏任务结束,依次输出 0 1 6 11第一轮事件循环微任务执行注册在“微任务队列”里的微任务,遇到 then 执行其回调输出 2遇到 then 执行其回调输出 7此时第一轮事件循环微任务结束,依次输出 2 7第一轮事件循环结束,此时“微任务队列”已被清空,“宏任务队列”里有两个 setTimeout 的回调函数,第一个 setTimeout 被送入主线程执行栈第二轮事件循环第二轮事件循环宏任务开始执行第一个 setTimeout 回调函数遇到 console.log 输出 3遇到 new Promise 其回调函数作为同步任务直接执行遇到 console.log 输出 4遇到 then 回调函数作为异步任务进入“微任务队列”此时第二轮事件循环宏任务结束,依次输出 3 4第二轮事件循环微任务执行注册在“微任务队列”里的微任务,遇到 then 执行其回调输出 5此时第二轮事件循环微任务结束,输出 5第二轮事件循环结束,此时“微任务队列”已被清空,“宏任务队列”里剩下一个 setTimeout 的回调函数,其被送入主线程执行栈第三轮事件循环第三轮事件循环宏任务开始执行第二个 setTimeout 回调函数遇到 console.log 输出 8遇到 new Promise 其回调函数作为同步任务直接执行遇到 console.log 输出 9遇到 then 回调函数作为异步任务进入“微任务队列”此时第三轮事件循环宏任务结束,依次输出 8 9第三轮事件循环微任务执行注册在“微任务队列”里的微任务,遇到 then 执行其回调输出 10此时第三轮事件循环微任务结束,输出 10第三轮事件循环结束,此时“微任务队列”已被清空,“宏任务队列”已被清空至此整段代码执行完毕,完整输出结果为:0 1 6 11 2 7 3 4 5 8 9 10Node.JS 环境Node.JS 环境下任务队列有层级之分,按层级执行任务队列第一轮事件循环第一轮事件循环宏任务开始执行代码,遇到 console.log 输出 0接着遇到 new Promise 其回调函数作为同步任务直接执行遇到 console.log 输出 1遇到 then 回调函数作为异步任务进入“微任务队列”接着遇到 setTimeout 其回调函数注册到 timer 阶段接着遇到 new Promise 其回调函数作为同步任务直接执行遇到 console.log 输出 6遇到 then 回调函数作为异步任务进入“微任务队列”接着遇到 setTimeout 其回调函数注册到 timer 阶段最后遇到 console.log 输出 11此时第一轮事件循环宏任务结束,依次输出 0 1 6 11第一轮事件循环微任务执行注册在“微任务队列”里的微任务,遇到 then 执行其回调输出 2遇到 then 执行其回调输出 7此时第一轮事件循环微任务结束,依次输出 2 7第一轮事件循环结束,此时“微任务队列”已被清空,timer 队列里有两个 setTimeout 的回调函数第二轮事件循环第二轮事件循环 timer 阶段执行第一个 setTimeout 回调函数遇到 console.log 输出 3遇到 new Promise 其回调函数作为同步任务直接执行遇到 console.log 输出 4遇到 then 回调函数作为异步任务进入“微任务队列”执行第二个 setTimeout 回调函数遇到 console.log 输出 8遇到 new Promise 其回调函数作为同步任务直接执行遇到 console.log 输出 9遇到 then 回调函数作为异步任务进入“微任务队列”此时第二轮事件循 timer 阶段结束,依次输出 3 4 8 9第二轮事件循环 timer 阶段微任务执行注册在“微任务队列”里的微任务遇到 then 执行其回调输出 5遇到 then 执行其回调输出 10此时第二轮事件循环 timer 阶段微任务结束,输出 5 10第二轮事件循环结束,至此整段代码执行完毕,完整输出结果为:0 1 6 11 2 7 3 4 8 9 5 10参考文章阮一峰 - JavaScript 运行机制详解:再谈Event Loop阮一峰 - Node 定时器详解Philip Roberts - Help,I’m stuck in an event looplynnelv - 深入理解js事件循环机制(Node.js篇)这一次,彻底弄懂 JavaScript 执行机制