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 语言在前端的应用前景>的内容分享. 本文是 ...
随机推荐
- ss客户端以及tcp,udp,dns代理ss-tproxy本地安装版--centos7.3 x64以上(7.3-7.6x64测试通过)
因为下载的文件,从cn下载很慢,或者下不动,所以我弄了一个本地安装版 本地安装的文件,我是从网上单独下载了,这里就不提供了. 记得在最后设置允许访问的局域网IP段 ## iptables 配置ipta ...
- 干掉hao123劫持浏览器主页
原因可能是安装某个软件流氓捆绑了IE主页导致的,建议这样尝试: 一.如果安装有三方安全防护类软件,排查流氓软件,建议运行系统自带的Windows Defender或者MSE程序扫描系统. 二.如果有检 ...
- Cache基本原理之:结构
转载自:https://www.jianshu.com/p/2b51b981fcaf Cache entries 数据在主存和缓存之间以固定大小的”块(block)”为单位传递,也就是每次从main ...
- Nginx 安装配置教程
1.安装 Nginx 的先决条件 依赖库:GCC, PCRE, zlib, OpenSSL . * GCC (Nginx 由 C 语言编写,因此需要在系统上安装一个编译工具) 基本上 Linux 自带 ...
- (转)适用微信小程序的table表格(带隔行变色)
原文地址 table.wxml <view class="table"> <view class="tr bg-w"> <view ...
- 代码: 两列图片瀑布流(一次后台取数据,图片懒加载。下拉后分批显示图片。图片高度未知,当图片onload后才显示容器)
代码: 两列图片瀑布流(一次后台取数据,无ajax,图片懒加载.下拉后分批显示图片.图片高度未知,当图片onload后才显示容器) [思路]: 图片瀑布流,网上代码有多种实现方式,也有各类插件.没找到 ...
- leetcode20
public class Solution { Stack<char> S = new Stack<char>(); public bool IsValid(string s) ...
- leetcode84
public class Solution { public int LargestRectangleArea(int[] hist) { // The main function to find t ...
- phpstudy+dvwa配置
1.Apache端口被系统进程占用(Pid一般为4) -- 修改apache端口,然后通过localhost:端口号访问 2.DVWA System error - config file not f ...
- JS StartMove源码-简单运动框架
这几天学习js运动应用课程时,开始接触一个小例子:“仿Flash的图片轮换播放器”,其中使用的StartMove简单运动框架我觉得挺好用的.这个源码也简单,理解其原理,自己敲即便也就熟悉了. 用的时候 ...