Tasks, microtasks, queues and schedules

有些东西不时常提起很可能会遗忘掉,建议自己经常记录一些!非原文 表示 个人见解,文章翻译部分章节。 本文翻译自 https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/  感谢jake提供的分享,版权归原作者所有,仅供学习参考使用!

Try it

  首先来看一段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');
script start
script end
promise1
promise2
setTimeout

正确答案是 script startscript endpromise1promise2setTimeout 

原文中提到可能浏览器的差异 可能setTimeout 会在 promise1 与 promise2之前打印,在firefox与Safari 8.0.7中总是正确的。(非原文:浏览器的差异本文中不考虑,具体查看原文)。

Why this happens

要理解这一点,您需要知道事件循环(event loop)如何处理任务和微任务。当你第一次遇到它的时候,你可能会有很多的想法。
请保持深呼吸……

每个“线程”都有自己的事件循环(event loop),因此每个web worker都有自己的事件循环(event loop),因此它可以独立执行,然而,同源上的所有windows窗口都共享一个事件循环(event loop),保证它们可以同步通信。事件循环(event loop)持续 执行队列中的任何任务。事件循环(event loop)可以有多个任务源,也需确保各个任务源的执行顺序(IndexedDB等规范定义了它们自己的执行顺序),但是浏览器可以决定在循环的每个回合中选择哪个源来执行任务(非本文:回答为什么会有不同的执行顺序。这允许浏览器优先处理性能敏感的任务,比如用户输入等。

Task 任务被调度,这样浏览器就可以从内部获取JavaScript/DOM,并确保这些操作按顺序发生。在任务之间,浏览器可能呈现更新。从鼠标单击到事件回调需要调度任务,解析HTML以及上述setTimeout 一样。setTimeout等待一个给定的延迟,然后为它的回调安排一个新的任务。这就是为什么在script end后打印 setTimeout,因为打印script end 是第一个任务的一部分,并且setTimeout记录在一个单独的任务中。

 MicroTask 微任务通常被安排在当前执行脚本之后应该立即发生的事情上,例如对一批操作做出反应,或者使某些东西异步执行,而不需要承担整个新任务的代价。只要没有其他JavaScript在执行过程中,并且在每个任务的末尾,微任务队列在回调之后被处理。在微任务期间排队等待的任何其他微任务都被添加到队列的末尾并被处理。微任务包括 mutation observer 回调, promise 回调等。 一旦一个promise设定了,或者它已经resolve,就把它加入microTask队列中。这确保了promise 回调是异步的,即使promise 已经resolve。调用 .then(yey, nay)  让promise任务立即步入 microtack的任务队列中。这就是为什么promise1和promise2会在 script end 之后被打印,因为当前运行的脚本必须在微任务处理之前完成。promise1和promise2在setTimeout之前被打印,因为微任务总是在下一个任务之前发生

STEPS

一步一步的图示

console.log('script start')
 TasksRun script执行JS 代码,打印输出script start
MicroTasks 
JS stackscript
Logscript start
setTimeout(function(){
  console.log('setTimeout')
},0)

Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});
 TasksRun script  setTimeout-callback

setTimeout的回调会被加入到Task中,

promise 回调加入到Microtasks中

MicroTaskspromise then
JS stackscript
Logscript start
console.log('script end')
 TasksRun script  setTimeout-callback

打印 script end, 已经到最后一行代码了

JS stack 中执行完成,开始执行micro task

Run script一致存在一直在当前任务执行完成后

MicroTaskspromise then
JS stack 
Logscript start  script end

执行microTask任务

 TasksRun script  setTimeout callback

执行完成第一个promise 后return undefined,

继续 将下一个promise callback 加入micro task

MicroTaskspromise then promise then
JS stackpromise callback
Logscript start  script end promise1
TasksRun script  setTimeout callback执行完之后清空
MicroTasks promise then
JS stack 
logscript start  script end promise1

重复以上步骤,执行完promise then 打印 promise2 后

TasksRun script  setTimeout callback
MicroTasks 
JS stack 
Logscript start  script end promise1 promise2

    上面提到过:微任务总是在下一个任务之前发生 。所有的微任务执行完成后,在执行下一个任务之前(例如,浏览器预备重新渲染之前)此时第一个RunScript的任务也即是 执行完成了。

Tasks setTimeout callback
MicroTasks 
JS stack 
logscript start  script end promise1 promise2
Tasks setTimeout callbacksetTimeout callback 任务进入JS stack
MicroTasks 
JS stacksetTimeout callback
Logscript start  script end promise1 promise2 setTimeout
Tasks 结束
MicroTasks 
JS stack 
Logscript start  script end promise1 promise2 setTimeout

----------------------------------------------------------------------------------------------------------------------------------------

接下来继续分析另外一段代码

<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);

mutationObser主要用来监听DOM的变动,callback会在每次DOM变动后调用,observe中第一个参数表示监听的DOM元元的article 第二个表示变动的类型 attribute:true表示监听属性的变动;前文提到过mutationObserve也是microTask。

继续分析下:触发click事件,setTimeout  是tasks,  mutation observe和 promise是microtasks

Tasks

Dispatch click  setTimeout callback

 

触发事件后,接收JS stack当前为onClick ,分析onClick函数中的代码执行将任务分配如下,console.log直接执行,输出click

pormise 加入microtasks

修改DOM后mutationObserver的任务也会加入到microtask中

MicroTaskspromise then Mutationobservers
JS stackonClick
Logclick

onClick已经执行完,微任务总是在下一个任务之前发生

Tasks

Dispatch click  setTimeout callback

 

在JS stack为空的时候执行下一个任务前执行 micro task,具体分析如上
MicroTasks 
JS stack 
Logclick promise mutate

但是由于冒泡的原因 会再次的onCick回调

Tasks

Dispatch click  setTimeout callback

setTimeout callback

注意新加的setTimeout callback
MicroTaskspromise then  mutation observers
JS stackonClick
Logclick promise mutate click
Tasks

Dispatch click  setTimeout callback

setTimeout callback

执行逻辑同上
MicroTasks 
JS stack 
Logclick promise mutate click promise mutate
Tasks

 

没有微任务后会继续执行下一个任务
MicroTasks 
JS stack 
Logclick promise mutate click promise mutate timeout timeout

---------------------------------------------------------------------------------------------------------

加深了印象之后进一步的深入

inner.click

注直接click的时候与Run Script是同步的,之前的是runScript 之后 dispatch click;在JS stack为空的时候执行下一个任务前执行 micro task

TasksRun script setTimeout callback

在执行完innerClick后, 此时stack并没有空,冒泡 会触发另一个 onClick,此时仍然实在Run Script阶段

mutation observer已经pending无法加入再次加入microtask

MicroTaskspromise then Mutation observe promise then
JS stackscript  onClick
Logclick

一切只有当同步的执行完之后,Run script结束 JS stack为空去执行下一个任务时会清空microtask 中的任务,

 

You made it!

总结:

1.Tasks 按顺序执行,浏览器可以在两个任务之间进行渲染

2.microTasks 按下规则执行:

   在所有回调执行完成(如:冒泡时会触发两次回调) ,并且在没有其他的JavaScript在运行中的时候

   在每一个任务的最后

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值