JavaScript是单线程,也就是说JS的堆栈中只允许有一类任务在执行,不可以同时执行多类任务。在读js文件时,所有的同步任务是一条task,当然了,每一条task都是一个队列,按顺序执行。而如果在中途遇到了setTimeout这种异步任务,就会将它挂起,放到任务队列中去执行,等执行完毕后,如果有callback,就把callback推入到Tasks中去,注意,是把异步任务的完成时的callback推进去,等待执行,而microtasks什么时候执行呢?只要JS stack栈清了,它就执行,它和异步任务不一样的是,它不会新开一个任务队列,就是新开一个task。常见的microtask有promise事件,MutationObserver对象。

这里我补充一下MutationObserver对象:就是监控某个范围内的DOM树如果发生变化时就会触发一些相应的事件,这个是DOM4里面定义的,用来替换DOM3里的Mutation事件,兼容性移动端没什么问题,安卓4.4,ios 6/7,但是IE就比较惨了,只能11以上。

常见的用法如下:

// Firefox和Chrome早期版本中带有前缀
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver // 选择目标节点
var target = document.querySelector('#some-id'); // 创建观察者对象
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log(mutation.type);
});
}); // 配置观察选项:
var config = { attributes: true, childList: true, characterData: true } // 传入目标节点和观察选项
observer.observe(target, config); // 随后,你还可以停止观察
observer.disconnect();

这里其实只要关注几点:

第一是构造函数的第一个参数是一个函数,就是回调,但是这个回调要注意的是,它不是立即回调,而是等所有的变化都结束的时候才会统一回调,这里比较坑的就是这个,所谓的所有变化都结束实际上就是指一个task里面,

第二是如果前一个mutation还没有触发回调,那么后续的mutation也不会触发。

回到上面。

所谓的event loop之所以说是一个循环,是因为每次JavaScript执行完Tasks队列中的一个task之后,它都会回过头去看一下是否有待执行的task了,如果有那就继续执行,这是一个不断循环重复的过程,因为你同步任务task执行完成后,挂起的异步任务可能还在任务队列里执行,暂时还没有callback,而异步任务执行完成后,callback开一个新的task,然后进入到Tasks队列中等候,而JS却不知道你已经在那儿排队了,所以没办法,只能通过不断循环的方式来确保每一个待执行的task都能被及时执行。它的逻辑大概如下,具体的下篇再讲:

while (queue.waitForMessage()) {
queue.processNextMessage();
}

这里只是做一个类比。

其实通过上面这个机制,我们可以看出一些问题:

比如:

1,setTimeout一定准时吗,不一定,如果它前面的task执行时间超过了它设置的时间,那它必须得等那个task执行完成之后才能执行,第二个参数的时间值并不代表该时间以后执行setTimeout callback,而是该时间以后将callback这个新的task推入到Tasks里面去等待执行。所以不要写什么setTimeout 0这种以为能立即执行的了,而且W3C规范,setTimeout最小值只能为4。

2,如果我有一个异步队列callback特别长,要执行好久好久,而此时我又触发了一个新的事件,那怎么办?没办法啊,只能等它这个callback执行完才能执行啊,就比如你是一个click事件,你触发了click对应的function,将这个新的task推入到Tasks中去,而此时并不会立即执行,因为前面还有没执行完的任务,所以会造成点击没效果,因为它还在等待前一个异步callback执行完。所以不要以为异步任务是异步的,就可以随意写一堆逻辑,如果太复杂了,也会造成用户操作没反应这种问题,虽然比较少。

3,关于click事件,这里单独说一下,看一个例子:

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

如果此时我点击里面那个小块儿inner,控制台会打印什么?

click
promise
mutate
click
promise
mutate
timeout
timeout

这里为什么会触发两次这个函数,很简单,因为冒泡,但是我们能看出一个问题,promise和mutate是微任务,只有JS主线程栈空了它们才会执行,说明每一次冒泡执行完后都会空一下,但是这里要注意的是,虽然主线程空了,但是不代表这里的click事件因为冒泡而被分成了两个task,实际上还是一个task(叫Dispatch click),而timeout虽然很快,但还是排在了两次冒泡的Dispatch click后面,因为前面是一个task,它是新开的一个task,必须等前面一个task执行完毕。那这里也能看出,微任务不一定是在当前task结束的时候才会执行,这里来看个图

这里可以看出,所有的微任务都在Microtasks这个队列中等待执行,只要JS stack一清空,它就立即执行,而是否一定是某个task执行完成呢,不一定,只要空了,它就执行。上面那个click冒泡就是很好的证明。但是可以确定的是,微任务一定是在一次事件循环event loop的结尾处执行。

下面来看一个坑爹的,还是上面那些代码,如果不是人为主动点击触发,而是改用js主动触发事件,就比如inner.click(),会是什么结果呢?来看:

click
click
promise
mutate
promise
timeout
timeout

意不意外?惊不惊喜?timeout就不解释了,同上,但是为什么click是连续执行了,为什么mutate只执行了一次,而且还是夹在了promise中间,针对这几个点,解释一下:

1,为什么click连续执行两次:先来看为什么上面那个不是连续执行,原因很简单,因为js栈空了,所以先执行了微任务,那这次为什么没有先执行微任务,那就是说明JS stack没有空嘛,这里作者给出的解释是,click()会导致事件同步分派,所以调用的脚本.click()仍然处于回调之间的堆栈中。上述规则确保微任务不会中断正在执行的JavaScript。这意味着我们不会在两者之间处理Microtasks队列,而是在它们之后。。。。总之就是如果是js调用的函数,JS堆栈不会空。

2,为什么mutate只执行了一次:当第一次执行到mutate的时候,它被直接插进了Microtasks队列中等待执行,而刚刚说到了MutationObserver这个对象的实例有个特点,当前一个挂起的这个对象还没解决的时候,后续的是不会处理的,所以只有一次,因此只有一个mutate被夹到了两个promise之间。

以上就是整个Tasks,Microtasks,任务队列等等专业知识的解释。

关于event loop的详解,请看后续。

end

JavaScript:再谈Tasks和Microtasks的更多相关文章

  1. javascript --- 再谈词法分析

    javascript代码是如何执行的呢,分为六个步骤(就像把大象装进冰箱总共分几步?): 第一步:载入第一个js代码段(注:script标签对内的代码或是引用js代码,这也说明js并不是一行一行(单纯 ...

  2. JavaScript 再谈闭包

    之前有整理过一版关于闭包的概念,但感觉思路不是很清晰,是临时想起一些例子来讲的,今天再次来讲一下闭包. 闭包: 函数嵌套函数,内部函数可以引用外部函数的参数和变量 function aaa(a){ v ...

  3. 再谈JavaScript的数据类型问题

    JavaScript的数据类型问题已经讨论过很多次了,但许多人还有许多书仍然沿用着错误的.混乱的一些观点,所以就再细讲一回. 提及这个讨论的原因在于argb同学在我的MSN博客上的一段回复,又更早的起 ...

  4. 再谈JSON -json定义及数据类型

    再谈json 近期在项目中使用到了highcharts ,highstock做了一些统计分析.使用jQuery ajax那就不得不使用json, 可是在使用过程中也出现了非常多的疑惑,比方说,什么情况 ...

  5. 再谈angularJS数据绑定机制及背后原理—angularJS常见问题总结

    这篇是对angularJS的一些疑点回顾,是对目前angularJS开发的各种常见问题的整理汇总.如果对文中的题目全部了然于胸,觉得对整个angular框架应该掌握的七七八八了.希望志同道合的通知补充 ...

  6. 再谈前端HTML模板技术

    在web2.0之前,写jsp的时候虽然有es和JSTL,但是还是坚持jsp.后面在外包公司为了快速交货,还是用了php Smart技术. web2.0后,前端模板技术风行. 代表有如下三大类: Str ...

  7. 再谈DOMContentLoaded与渲染阻塞—分析html页面事件与资源加载

    浏览器的多线程中,有的线程负责加载资源,有的线程负责执行脚本,有的线程负责渲染界面,有的线程负责轮询.监听用户事件. 这些线程,根据浏览器自身特点以及web标准等等,有的会被浏览器特意的阻塞.两个很明 ...

  8. 再谈HTTP2性能提升之背后原理—HTTP2历史解剖

    即使千辛万苦,还是把网站升级到http2了,遇坑如<phpcms v9站http升级到https加http2遇到到坑>. 因为理论相比于 HTTP 1.x ,在同时兼容 HTTP/1.1 ...

  9. 再谈 Go 语言在前端的应用前景

    12 月 23 日,七牛云 CEO & ECUG 社区发起人许式伟先生在 ECUG Con 2018 现场为大家带来了主题为<再谈 Go 语言在前端的应用前景>的内容分享. 本文是 ...

随机推荐

  1. Java中的权限修饰符private、protected、public

    java中的修饰符分类: 权限修饰符: private, default, protected, public 状态修饰符: static, final 抽象修饰符: abstract 权限修饰符 我 ...

  2. HTML5 使用小结

    1.html5新增的常用元素 (a) <article.../>代表独立完整的一遍文章 (b)<section.../>对页面内容进行分块 (c)<nav.../> ...

  3. 层次softmax函数(hierarchical softmax)

    一.h-softmax 在面对label众多的分类问题时,fastText设计了一种hierarchical softmax函数.使其具有以下优势: (1)适合大型数据+高效的训练速度:能够训练模型“ ...

  4. Android Studio 制作一个循环播报的效果

    这个就是用到了一个TextView 控件,直接上代码. <TextView android:id="@+id/tv_7" android:layout_width=" ...

  5. thinkphp5 Exception类重定义

    重点定义自己的错误信息和错误码: 在TP5的配置文件中有下面一段 // 异常处理handle类 留空使用 \think\exception\Handle 'exception_handle' => ...

  6. Shell脚本1-20例

    1.每天生成一个文件 描述:请按照这样的日期格式(xxxx-xx-xx)每日生成一个文件,例如今天生成的文件为)2017-07-05.log, 并且把磁盘的使用情况写到到这个文件中,(不用考虑cron ...

  7. Mybatis的mapper文件中#和$的区别 以及 resultType和resultMap的区别

    一般#{}用于传递查询的参数,一般用于从dao层传递一个string或者其他的参数过来,mybatis对这个参数会进行加引号的操作,将参数转变为一个字符串. SELECT * FROM employe ...

  8. TCC细读 - 1 例子流程

    http://www.iocoder.cn/categories/TCC-Transaction/ https://github.com/changmingxie/tcc-transaction 细读 ...

  9. SQL With (递归CTE查询)

    指定临时命名的结果集,这些结果集称为公用表表达式 (CTE).该表达式源自简单查询,并且在单条 SELECT.INSERT.UPDATE 或 DELETE 语句的执行范围内定义.该子句也可用在 CRE ...

  10. jmeter 测试计划

    进行 jmeter 测试时首先都要有一个测试计划,测试计划下的一些名词解释: