我是在作前端面试题中看到了setTimeout和Promise的比较,而后第一次看到了microtask和macrotask的概念,在阅读了一些文章以后发现没有一个比较全面易懂的文章,因此我尝试作一个梳理性的总结.javascript
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');
首先咱们先弄清楚setTimeout和Promise的共同点,也就是我第一次的看到那道面试题的疑惑点.html
JavaScript 主线程拥有一个 执行栈 以及一个 任务队列,主线程会依次执行代码,当遇到函数时,会先将函数 入栈,函数运行完毕后再将该函数 出栈,直到全部代码执行完毕。前端
上面的例子的执行栈执行顺序应该是这样的java
console.log('script start'); console.log('script end'); Promise.resolve();
而任务队列的执行顺序应该是这样的git
Promise.then(function() { console.log('promise1'); }); Promise.then(function() { console.log('promise2'); }); setTimeout(function() { console.log('setTimeout'); }, 0);
而主线程则会在 清空当前执行栈后,按照先入先出的顺序读取任务队列里面的任务。github
众所周知setTimeout和Promise.then()都属于上述异步任务的一种,那到底为何setTimeout和Promise.then()会有顺序之分,这就是我想分析总结的问题所在了.web
tasks的做用是为了让浏览器可以从内部获取javascript / dom的内容并确保执行栈可以顺序进行。面试
tasks的调度是随处可见的,例如解析HTML,得到鼠标点击的事件回调等等,在这个例子中,咱们所迷惑的setTimeout也是一个tasks.api
microtasks一般用于在当前正在执行的脚本以后直接发生的事情,好比对一系列的行为作出反应,或者作出一些异步的任务,而不须要新建一个全新的tasks。promise
只要执行栈没有其余javascript在执行,在每一个tasks结束时,microtasks队列就会在回调后处理。在microtasks期间排队的任何其余microtasks将被添加到这个队列的末尾并进行处理。
microtasks包括mutation observer callbacks,就像上例中的promise callbacks同样。
须要注意的是,在两个tasks之间,浏览器会从新渲染。这也是咱们须要了解tasks和microtasks的一个很是重要的缘由.
Vue 中如何使用 MutationObserver 作批量处理? - 顾轶灵的回答 - 知乎根据 HTML Standard,在每一个 task 运行完之后,UI 都会重渲染,那么在 microtask 中就完成数据更新,当前 task 结束就能够获得最新的 UI 了。反之若是新建一个 task 来作数据更新,那么渲染就会进行两次。
在__Microsoft Edge__, Firefox 40__, __iOS Safari 以及 desktop Safari 8.0.8 中setTimeout会先于Promise
//html <div class="outer"> <div class="inner"></div> </div>
// Let's get hold of those elements var outer = document.querySelector('.outer'); var inner = document.querySelector('.inner'); // Let's listen for attribute changes on the // outer element new MutationObserver(function() { console.log('mutate'); }).observe(outer, { attributes: true }); // Here's a click listener… function onClick() { console.log('click'); setTimeout(function() { console.log('timeout'); }, 0); Promise.resolve().then(function() { console.log('promise'); }); outer.setAttribute('data-random', Math.random()); } // …which we'll attach to both elements inner.addEventListener('click', onClick); outer.addEventListener('click', onClick);
在这个例子中,不一样浏览器的log是不一样的,以下所示
Chrome | Firefox | Safari | edge |
---|---|---|---|
click | click | click | click |
promise | mutate | mutate | click |
mutate | click | click | mutate |
click | mutate | mutate | timeout |
promise | timeout | promise | promise |
mutate | promise | promise | timeout |
timeout | promise | timeout | promise |
timeout | timeout | timeout | \ |
事实上Chrome是正确的,并且由此可发现microtasks并非在tasks的结束阶段开始执行,而是在tasks中回调结束以后(只要没有正在执行的JavaScript代码)
tasks会顺序执行,浏览器会在执行间隔从新渲染
microtasks会顺序执行,执行时机为
在没有JavaScript代码执行的callback以后在每个tasks以后
因为我是前端初学者,对于JavaScript还很不熟悉,对事件循环的进程模型不是很了解,但愿这篇文章可以帮助你们.
事件循环机制建议参考文章
阮一峰-->JavaScript 运行机制详解:再谈Event Looptasks建议参考文章
Jake Archibald-->Tasks, microtasks, queues and schedules