今天看了一篇文章讲述的关于任务,微任务,队列之类的文章觉得非常有用在这里谨添加上自己的理解,原文在这里

Task,Microtask,queues,schedules在这里分别翻译成:任务,微任务,队列,计划。

以下代码输出什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
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');

这道题目会出现在面试题之中,一般来说会输出:

1
2
3
4
5
script start
script end
promise1
promise2
setTimeout

为什么会这样呢?以下为摘抄自原文:

每个线程都有其事件循环,所以每个 web worker(工作线程)也有。这样它就可以独立执行,但是同一个源上的所有窗口共享一个事件循环,因为他们可以同步通信。事件循环一直运行,执行排队的任务。一个事件循环拥有多个任务源以保证任务的顺序执行,但是浏览器在每次循环的时候会选择从哪个源选择任务来执行。这样允许浏览器优先执行对性能敏感的任务比如用户输入。

任务 是浏览器可以从其中获取内部构件从而执行JavaScript/DOM 作用域并且保证这些操作都顺序执行。在任务之间,浏览器可能会渲染更新。鼠标的点击事件回调,比如解析 HTML, setTimeout都会需要创建任务。setTimeout是在一个时间间隔后为其回调安排一个任务。这样那个setTimeout(function() { console.log('setTimeout');}, 0)就会在script end最后输出。

微任务一般是指在当前执行脚本执行完毕的时候立即执行的任务,比如批量操作之后的动作,或者不以创建一个新任务的性能作为代价来让某些事件异步执行。微任务队列的执行时机是在回调之后执行的因为在这期间没有其它 JavaScript 脚本在执行,并且必须是在每个任务结束之前。在微任务运行期间添加的其它排队的微任务会添加在微任务队列的末尾并且执行。微任务包括mutation observer回调和 promise 回调。
MutationObserver是异步的。

关于 Mutataion 可以查看这里

当 promise 创建以后,会排入微任务队列中。所以 promise1 和 promise2 会在 setTimeout 之前输出,因为微任务问题会发生在下一个任务之前,当前任务结束之后。

总结

  • 任务会顺序执行,浏览器会在任务之间渲染
  • 微任务会顺序执行,执行时机是在每个回调之后并且期间没有其它 JavaScript 在执行并且必须是在每个任务结束之前

这里有一个很有意思的地方是原文下文的评论有人提问的关于那个es6-promise兼容库使用的setImmediateprocess.nextTick方法。前一个是属于任务,后一个差不多是微任务。那么如果写过 vue 的家伙应该对这个 nextTick 很熟悉吧?那么为什么 vue 的数据更新是异步的呢?因为他应该是用了 MutationObserver 所有的操作是异步缓存入队列再在一定的时间内 flush 到 DOM 中,如果使用 nextTick 意思即再插入一个微任务,这个微任务是在对 DOM 更新完毕后执行然后就可以获取到 DOM 更新后的元素内容。是这么理解的吗?。欢迎指正。

疑问

原文说的微任务执行时机:

after every callback, as long as no other JavaScript is mid-execution

这里的回调指的是什么?是指每个 JavaScript 堆栈还是什么?不理解。