JavaScript:再谈Tasks和Microtasks
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的更多相关文章
- javascript --- 再谈词法分析
javascript代码是如何执行的呢,分为六个步骤(就像把大象装进冰箱总共分几步?): 第一步:载入第一个js代码段(注:script标签对内的代码或是引用js代码,这也说明js并不是一行一行(单纯 ...
- JavaScript 再谈闭包
之前有整理过一版关于闭包的概念,但感觉思路不是很清晰,是临时想起一些例子来讲的,今天再次来讲一下闭包. 闭包: 函数嵌套函数,内部函数可以引用外部函数的参数和变量 function aaa(a){ v ...
- 再谈JavaScript的数据类型问题
JavaScript的数据类型问题已经讨论过很多次了,但许多人还有许多书仍然沿用着错误的.混乱的一些观点,所以就再细讲一回. 提及这个讨论的原因在于argb同学在我的MSN博客上的一段回复,又更早的起 ...
- 再谈JSON -json定义及数据类型
再谈json 近期在项目中使用到了highcharts ,highstock做了一些统计分析.使用jQuery ajax那就不得不使用json, 可是在使用过程中也出现了非常多的疑惑,比方说,什么情况 ...
- 再谈angularJS数据绑定机制及背后原理—angularJS常见问题总结
这篇是对angularJS的一些疑点回顾,是对目前angularJS开发的各种常见问题的整理汇总.如果对文中的题目全部了然于胸,觉得对整个angular框架应该掌握的七七八八了.希望志同道合的通知补充 ...
- 再谈前端HTML模板技术
在web2.0之前,写jsp的时候虽然有es和JSTL,但是还是坚持jsp.后面在外包公司为了快速交货,还是用了php Smart技术. web2.0后,前端模板技术风行. 代表有如下三大类: Str ...
- 再谈DOMContentLoaded与渲染阻塞—分析html页面事件与资源加载
浏览器的多线程中,有的线程负责加载资源,有的线程负责执行脚本,有的线程负责渲染界面,有的线程负责轮询.监听用户事件. 这些线程,根据浏览器自身特点以及web标准等等,有的会被浏览器特意的阻塞.两个很明 ...
- 再谈HTTP2性能提升之背后原理—HTTP2历史解剖
即使千辛万苦,还是把网站升级到http2了,遇坑如<phpcms v9站http升级到https加http2遇到到坑>. 因为理论相比于 HTTP 1.x ,在同时兼容 HTTP/1.1 ...
- 再谈 Go 语言在前端的应用前景
12 月 23 日,七牛云 CEO & ECUG 社区发起人许式伟先生在 ECUG Con 2018 现场为大家带来了主题为<再谈 Go 语言在前端的应用前景>的内容分享. 本文是 ...
随机推荐
- weblogic发序列化命令执行漏洞工具分享
weblogic发序列化命令执行漏洞工具分享(链接: https://pan.baidu.com/s/1qE5MFJ32672l-MMl-QL-wQ 密码: d85j) JBOSS_EXP 工具分享( ...
- MySQL Antelope和Barracuda的区别分析
Antelope是innodb-base的文件格式,Barracude是innodb-plugin后引入的文件格式,同时Barracude也支持Antelope文件格式.两者区别在于: 文件格式 支持 ...
- nodejs+mocha+supertest+chai进行测试(only demo)
1.nodejs安装成功 (上一篇:brew install nodejs) 2.mocha安装成功 npm install -g mocha 解释: -g代表global,全局的意思.此处mocha ...
- leetcode14:最长公共字符串
编写一个函数来查找字符串数组中的最长公共前缀. 如果不存在公共前缀,返回空字符串 "". 示例 1: 输入: ["flower","flow" ...
- linux下启动tomcat服务
Linux下tomcat服务的启动.关闭与错误跟踪,使用PuTTy远程连接到服务器以后,通常通过以下几种方式启动关闭tomcat服务:切换到tomcat主目录下的bin目录(cd usr/local ...
- Object.create() vs new SomeFunction() in javascript
Object.create builds an object that inherits directly from the one passed as its first argument. Wit ...
- 最新ceph集群常用命令梳理
结合网络.官网.手动查询等多方渠道,整理ceph维护管理常用命令,并且梳理常规命令在使用过程中的逻辑顺序.另外整理期间发现ceph 集群的命令体系有点乱,详细情况各自体验. 一:ceph集群启动.重启 ...
- django AnonymousUser
AnonymousUser对象class models.AnonymousUser django.contrib.auth.models.AnonymousUser是一个实现django.contri ...
- ide调试
F8: 程序向下执行一行(如果当前行有方法调用,这个方法将被执行完毕返回,然后到下一行) F7: 程序向下执行一行.如果该行有自定义方法,则运行进入自定义方法(不会进入官方类库的方法) Alt + ...
- 数据导入Excel时,出现ole error 800AC472这个错误,怎么解决。
我也出现过这个问题 在生成报表的时候不要动EXCEL中的任何单元格 让它完成保存就可以了 或者是把office 2003 删除下载一个office 2000就可以解决 据说是版本兼容的问题 不是高手 ...