浏览器环境下的microtaks和macrotasks
带有可视代码执行顺序的原文链接https://jakearchibald.com/201...,
此篇文字并非其完整翻译,加入了一部分自己的理解,比如将其中的task替换为macrotask或是删除了可视代码执行顺序的逐步解释。
运行顺序
参考以下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
*/
但是,在 Microsoft Edge, Firefox 40, iOS Safari 和 桌面版 Safari 8.0.8 中,setTimeout会优先于promise1和promise2。而令人奇怪的是,在 Firefox 39 和 Safari 8.0.7 中又是一致的。
为什么会这样
- Macrotask
想要理解这部分内容,你需要知道事件循环和microtasks。如果你是第一次接触相关内容,可能会需要一些精力,别紧张,大家都会这样,深呼吸…
在浏览器中,每一个thread(可以理解为每一个页签)都有自己的事件循环,因此,它们可以相互独立执行自身的Macrotask,然而,同源的窗口会分享同一个事件循环来保证相互可以进行同步通讯行为。事件循环会持续运行下去,用于执行当前存在的所有任务列表。每一个事件循环存在多个不同的任务队列用以保证执行顺序,而浏览器会依照任务类别来从任务序列中选取一个任务来进行执行。这使得浏览器可以优先选择执行更为重要的任务,比如用户输入操作。
Macrotask是已经被排序完成的,因此浏览器可以通过内部的机制来直接将其放置于javascript/DOM程序域中并确保每一个程序步骤的顺序执行。而在两个任务执行间隔之中,浏览器 可能 会执行更新操作。比如处理获取用户点击的回调函数,分析HTML,又或者是setTimeout。
setTimeout等待一个指定的时间延迟然后加入一个新的任务来执行对应的回调函数。这就是为什么setTimeout会延迟于script end,因为script end是第一个任务的程序内容,而setTimeout是来之后续的另一个任务。
- Microtasks
Microtasks通常用于排列那些应当在当前任务执行完毕后立即执行的任务,比如对某些事件作出反应,或是一些不会影响新任务的异步操作。这个Microtasks序列是在没有其他JavaScript任务正在执行,同时在其他Macrotask执行完毕之后。任何新添加的Microtasks会被排列到Microtasks的队尾并进行处理。promise的回调函数正是处于Microtasks队列之中。
当一个promise结束掉以后,或者它在之前已经处理完毕,那么会添加一个回馈结果的回调函数至Microtasks的队尾。这确保了promise的回调函数永远是异步执行的,即使promise已经在当前的时间片执行完毕。因此在调用.then(yey,nay)时并不会直接将一个Macrotask添加至队尾。这就是为什么promise1和promise2会晚于script end,当前运行的Macrotask一定会在Macrotask处理前执行完毕。promise1和promise2早于setTimeout输出,则是因为microtasks永远在下一个Macrotask启动前结束。
为什么有些浏览器表现不一致
有些浏览器的输出顺序为:script start, script end, setTimeout, promise1, promise2。它们在执行setTimeout后才运行primise的回调函数。这就好像是它们更倾向于将promise的回调函数看做Macrotask的一类。
这其实是可以理解的,promise是来自于ECMAScript而非HTML。ECMAScript拥有一个类似于Macrotask的"jobs"的概念,但这种关系并不能很清晰的区分开vague mailing list discussions。无论如何,更为普遍的观点是,promise是属于microtask,并且有一些很好的理由。
将promise看做Macrotask会导致性能问题,回调函数可能会因为渲染等相关Macrotask产生不必要的延后。同时也会导致影响其他的Macrotask,并且可能打断和其他api的交互,并导致其延后。
这里有个将promise当做microtasks处理的类似说明,an Edge ticket。WebKit内核的做法显然是正确的,因此我推断Safari最终也会选择修复这个问题,同时Firefox43似乎也已经修复了这个问题。
如何判断是Macrotask还是Microtask
直接进行测试是一种办法。在浏览器中直接查看关于promise和setTimeout的输出,尽管你依赖的实现是正确的。
就像之前所提到的,在ECMAScript中,它们称microtasks为“jobs”。在step 8.a of PerformPromiseThen中,EnqueueJob被称为添加一个microtask。
现在,让我们看一个更复杂的例子。
加入MutationObserver
首先让我们写一段html代码:
<div class="outer">
<div class="inner"></div>
</div>
接下来是一段JS:
// 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);
/*
*click
*promise
*mutate
*click
*promise
*mutate
*timeout
*timeout
*/
在不同浏览器中的表现:
Chrome:
click
promise
mutate
click
promise
mutate
timeout
timeout
FireFox:
click
mutate
click
mutate
timeout
promise
promise
timeout
Safari:
click
mutate
click
mutate
promise
promise
timeout
timeout
Edge:
click
click
mutate
timeout
promise
timeout
promise
哪个是正确的
抛出‘click’事件的是一个macrotask,Mutation observer 和 promise 的回调函数被当做microtask进行排列。setTimeout的回调会被当做一个 macrotask。
因此Chrome的运行结果才是正确的。这里有点奇特的地方反而是microtask在回调函数之后执行(直到没有其他的代码在执行),我认为这里是限制了marcotask的完成。这条用于限制回调函数的规则来源自HTML:
If the stack of script settings objects is now empty, perform a microtask checkpoint
— HTML: Cleaning up after a callback step 3
同时一个microtask checkpoint遍历了整个microtask队列,除非我们已经在执行microtask队列。类似的,ECMAScript 描述了jobs:
Execution of a Job can be initiated only when there is no running execution context and the execution context stack is empty…(Job可以在没有可执行环境和可执行环境的堆为空的情况下被初始化)
— ECMAScript: Jobs and Job Queues
尽管这里的“can be”在HTML环境中变成了“must be”。
浏览器是怎么出错的?
Firefox和Safari在两次点击操作之间运行完成了所有的microtasks,就比如mutation的回调函数所展示的,但是promise似乎有不同的排序算法。这是可以理解的,因为jobs和microtasks之间的联系是相对模糊的,但我依然可以确定他们会在两次点击回调操作之间运行完成。Firefox ticket.Safari ticket.
对于Edge我们已经可以确定它对于promise的队列类别是不正确的,但它依然在两次点击回调操作之间运行完成了所有的microtasks,相反的是它是在调用完成了所有的监听回调后,两次点击操作仅仅触发了一次mutate。Bug ticket
试试更复杂的
现在我们仅仅在代码最后加入一行新的代码来取代点击操作:
// 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();
这将会和上一个例子一样抛出点击事件,但我们使用代码来取代真实的点击交互。
试一试
Chrome:
click
click
promise
mutate
promise
timeout
timeout
FireFox:
click
click
mutate
timeout
promise
promise
timeout
Safari:
click
click
mutate
promise
promise
timeout
timeout
Edge:
click
click
mutate
timeout
promise
timeout
promise
为什么会这样
在所有的监听回调触发完成后…
If the stack of script settings objects is now empty, perform a microtask checkpoint
— HTML: Cleaning up after a callback step 3
在上一个的例子中,microtasks会在两个点击回调之间运行,但.click()使得两次事件顺序同步执行,因此在两次点击回调之间依然存在js代码在运行。而上面的规则确保了microtasks不会打断正在执行的代码片段。这意味着我们不能在两次点击监听之间执行microtasks队列,它们将会在监听回调执行完成后开始运行。
总结
- Macrotask会顺序执行,浏览器可能会在其执行间隔中进行渲染操作
Microtask会顺序执行:
- 在所有的回调完成之后,且不存在其他的js代码正在执行
- 在每一个macrotask完成之后
希望你现在已经清楚了事件循环的相关内容,或者至少可以去偷个懒休息一下。
浏览器环境下的microtaks和macrotasks的更多相关文章
- atitit.js浏览器环境下的全局异常捕获
atitit.js浏览器环境下的全局异常捕获 window.onerror = function(errorMessage, scriptURI, lineNumber) { var s= JSON. ...
- 浏览器环境下Javascript脚本加载与执行探析之DOMContentLoaded
在”浏览器环境下Javascript脚本加载与执行探析“系列文章的前几篇,分别针对浏览器环境下JavaScript加载与执行相关的知识点或者属性进行了探究,感兴趣的同学可以先行阅读前几篇文章,了解相关 ...
- 浏览器环境下JavaScript脚本加载与执行探析之动态脚本与Ajax脚本注入
在<浏览器环境下JavaScript脚本加载与执行探析之defer与async特性>中,我们研究了延迟脚本(defer)和异步脚本(async)的执行时机.浏览器支持情况.浏览器bug以及 ...
- 浏览器环境下JavaScript脚本加载与执行探析之defer与async特性
defer和async特性相信是很多JavaScript开发者"熟悉而又不熟悉"的两个特性,从字面上来看,二者的功能很好理解,分别是"延迟脚本"和"异 ...
- Node.js event loop 和 JS 浏览器环境下的事件循环的区别
Node.js event loop 和 JS 浏览器环境下的事件循环的区别: 1.线程与进程: JS 是单线程执行的,指的是一个进程里只有一个主线程,那到底什么是线程?什么是进程? 进程是 CPU ...
- 浏览器环境下的javascript DOM对象继承模型
这张图是我直接在现代浏览器中通过prototype原型溯源绘制的一张浏览器宿主环境下的javascript DOM对象模型,对于有效学习和使用javascript DOM编程起到高屋建瓴的指导作用, ...
- 浏览器环境下JavaScript脚本加载与执行探析之代码执行顺序
本文主要基于向HTML页面引入JavaScript的几种方式,分析HTML中JavaScript脚本的执行顺序问题 1. 关于JavaScript脚本执行的阻塞性 JavaScript在浏览器中被解析 ...
- 解析浏览器和nodejs环境下console.log()的区别
写在前面的 在开发调试过程中,我们经常需要调用console.log 方法来打印出当前变量的值,然而,console.log在浏览器环境下 有时会出现一些异常的现象 开撸代码 在浏览器和nodejs环 ...
- Javascript的二进制数据处理学习 ——nodejs环境和浏览器环境分别分析
以前用JavaScript主要是处理常规的数字.字符串.数组对象等数据,基本没有试过用JavaScript处理二进制数据块,最近的项目中涉及到这方面的东西,就花一段时间学了下这方面的API,在此总结一 ...
随机推荐
- 安装Nvida 显示环境
查看是否能正确加载nvidia 驱动 在终端输入 (glxinfo 需要安装mesa-utils) 如果可以正确加载了nvidia驱动 那么在输入的内容中可以看到NVIDIA 字样 如果GPU是Int ...
- vue之双向绑定
Vue的一大核心是双向绑定,在2.0中采用数据劫持,用Object.defineProperty实现,但作者已声明在3.0中会采用proxy实现 Object.defineProperty是什么? ...
- css border-raidus 百分比和数值设置效果不同
1.水平方向和竖直方向半径相等:设置数值和百分比的效果是一样的: 2.水平方向和竖直方向半径不相同:则效果不一致,具体参见:http://www.zhangxinxu.com/wordpress/20 ...
- centos6/7启动故障排错
centos6启动流程修复: 实验一:删除initramfs-2.6.32-754.el6.x86_64.img进行恢复 该文件很重要initramfs-2.6.32-754.el6.x86_64.i ...
- linux设置自动同步服务器时间
最近遇到一个问题,由于两台服务器时间的问题,经常导致用户登录由于时间差问题而报错,再三百度,最后整理了一下修改linux定时同步的操作(本方法适用于有自己时间服务器,没有的只限于借鉴) 首先确认,我们 ...
- PaaS容器集群优化之路
1. 性能优化面对的挑战 以下是整个PaaS平台的架构 其中主要包括这些子系统: 微服务治理框架:为应用提供自动注册.发现.治理.隔离.调用分析等一系列分布式/微服务治理能力,屏蔽分布式系统的复杂度. ...
- 用Python做一个飞机大战游戏
基于pygame的一款小游戏 这是我上半年做的一款小游戏,但是一直忘记了,现在才上传代码. github项目地址:StarMan 代码基于pygame,Python版本3.5.2运行正常. 游戏很简单 ...
- Flyme密码验证策略升级,忘记锁屏密码及「关机密码」功能
手机里有很多需要用到密码的地方,比如「手机密码」.「文档锁定区」.「应用加密」.「隐私模式」.忘记密码可是一件麻烦事,以前只能通过清除数据或格式化存储盘来解决.现在有了「关联魅族账号」功能,这些功 ...
- 快看,那个学SLAM 的崩溃了!
点"计算机视觉life"关注,置顶更快接收消息! 本文列举了当前优秀SLAM方案,点出了SLAM学习者的困境,最后打算搞点大事 请把此文转发给你认识的SLAM大神,愿你头发浓密,心 ...
- 求输入数字的阶乘 及加和 #s=1!+2!+3!+…..+n!
#s=1!+2!+3!+…..+n! from functools import reduce def factorial(n): result=0 for i in range(1,n+1): re ...