首先什么也不要去想,直接看代码并思考一个问题:它打印的顺序是什么?
1 console.log('script start'); 2 3 setTimeout(function () { 4 console.log('setTimeout'); 5 }, 0); 6 7 Promise.resolve() 8 .then(function () { 9 console.log('promise1'); 10 }) 11 .then(function () { 12 console.log('promise2'); 13 }); 14 15 console.log('script end');
看答案:

对错都不要着急,先了解下基本概念:
js是单线程的:单线程 : 同一时间下只能做一件事避免DOM渲染冲突
- 浏览器需要渲染DOM
- JS可以修改DOM结构
- JS执行的时候,浏览器DOM渲染会暂停
- 两端JS不能同时执行(都修改DOM就冲突了)
- webworker能实现多线程,但是不能访问DOM
宏任务(task)
浏览器为了能够使得JS内部 task 与DOM任务能够有序的执行,会在一个 task 执行结束后,在下一个 task 执行开始前,对页面进行重新渲染 (task->渲染->task->...)
比如:鼠标点击会触发一个事件回调,需要执行一个 task ,然后解析HTMl。
在上面的例子中:
setTimeout的作用是等待给定的时间后为它的回调产生一个新的宏任务。这就是为什么打印‘setTimeout’在‘script end’之后。因为打印‘script end’是第一个宏任务里面的事情,而‘setTimeout’是另一个独立的任务里面打印的。
微任务(Microtasks )
微任务通常来说就是需要在当前 task 执行结束后立即执行的任务。
比如:对一系列动作做出反馈,或者是需要异步的执行任务而又不需要分配一个新的 task,这样可以减小一点性能的开销。只要执行栈中没有其他的js代码正在执行且每个宏任务执行完,微任务队列会立即执行。如果在微任务执行期间微任务队列加入了新的微任务,会将新的微任务加入队列尾部,之后也会被执行。微任务包括了mutation observe的回调还有接下来的例子promise的回调。一旦一个pormise有了结果,或者早已有了结果(有了结果是指这个promise到了fulfilled或rejected状态),他就会为它的回调产生一个微任务,这就保证了回调异步的执行即使这个promise早已有了结果。所以对一个已经有了结果的promise调用.then()会立即产生一个微任务。这就是为什么‘promise1’,'promise2'会打印在‘script end’之后,因为所有微任务执行的时候,当前执行栈的代码(宏任务)必须已经执行完毕。‘promise1’,'promise2'会打印在‘setTimeout’之前是因为所有微任务总会在下一个宏任务之前全部执行完毕。
偷张图:😋

基于上面的例子来看下:
Event Queue:事件队列
- 首先浏览器执行js进入第一个宏任务进入主线程, 遇到 setTimeout 分发到第二个宏任务Event Queue中;
- 遇到 console.log() 直接执行 输出 script start;
- 执行 then 被分发到微任务Event Queue中;
- 遇到 console.log() 直接执行 输出 script end
- 第一个宏任务执行结束,开始执行第一个微任务打印 'promise1'promise2'
- 第一轮微任务执行完毕,执行第二个宏事件,打印 setTimeout 里面内容'setTimeout'
直接列出常见的宏任务与微任务:(不做标注代表web和node环境都支持)
宏任务:setTimeout setInterval setImmediate(web环境不支持) requestAnimationFrame(node环境不支持);
微任务:process.nextTick(web不支持) MutationObserver(node不支持) Promise.then catch finally
摘:https://www.cnblogs.com/ckAng/p/11133643.html
1 //主线程直接执行 2 console.log('1'); 3 //丢到宏事件队列中 4 setTimeout(function() { 5 console.log('2'); 6 process.nextTick(function() { 7 console.log('3'); 8 }) 9 new Promise(function(resolve) { 10 console.log('4'); 11 resolve(); 12 }).then(function() { 13 console.log('5') 14 }) 15 }) 16 //微事件1 17 process.nextTick(function() { 18 console.log('6'); 19 }) 20 //主线程直接执行 21 new Promise(function(resolve) { 22 console.log('7'); 23 resolve(); 24 }).then(function() { 25 //微事件2 26 console.log('8') 27 }) 28 //丢到宏事件队列中 29 setTimeout(function() { 30 console.log('9'); 31 process.nextTick(function() { 32 console.log('10'); 33 }) 34 new Promise(function(resolve) { 35 console.log('11'); 36 resolve(); 37 }).then(function() { 38 console.log('12') 39 }) 40 })
- 首先浏览器执行js进入第一个宏任务进入主线程, 直接打印console.log('1')
- 遇到 setTimeout 分发到宏任务Event Queue中
- 遇到 process.nextTick 丢到微任务Event Queue中
- 遇到 Promise, new Promise 直接执行 输出 console.log('7');
- 执行then 被分发到微任务Event Queue中
- 第一轮宏任务执行结束,开始执行微任务 打印 6,8
- 第一轮微任务执行完毕,执行第二轮宏事件,执行setTimeout
- 先执行主线程宏任务,在执行微任务,打印'2,4,3,5'
- 在执行第二个setTimeout,同理打印 ‘9,11,10,12’
- 整段代码,共进行了三次事件循环,完整的输出为1,7,6,8,2,4,3,5,9,11,10,12。
以上是在浏览器环境下执行的数据,只作为宏任务和微任务的分析,我在node环境下测试打印出来的顺序为:1,7,6,8,2,4,9,11,3,10,5,12。
node环境执行结果和浏览器执行结果不一致的原因是:浏览器的Event loop是在HTML5中定义的规范,而node中则由libuv库实现。libuv库流程大体分为6个阶段:timers,I/O callbacks,idle、prepare,poll,check,close callbacks,和浏览器的microtask,macrotask那一套有区别。
end...
浙公网安备 33010602011771号