宏任务和微任务

让我们从浏览器加载 script 说起,当浏览器加载完 script 之后,不考虑 script 标签的 defer 属性,script 将被立即执行。这时,我们就创建了一个宏任务。

在我们加载的代码中,可能有 click 事件的监听,也可能会发出网络请求。当这些操作触发我们埋下的回调函数后,相应的回调函数都会作为新的宏任务执行。

当然,在我们的宏任务的代码中,也可能会用到 Promise,MutationObserver 这些 api,他们的回调函数(或者 then 函数)都会在本轮宏任务结束之后,渲染进程工作之前执行,它们被称为微任务。

整个过程如下:

宏任务 => 微任务 => 渲染 => 宏任务 => 微任务 => 渲染 ...

这个循环往复的 过程在页面生命周期内一直存在,被称为 event loop。

一个例子

了解了什么是宏任务和微任务之后,我们来看一个经典的例子:

Promise.resolve().then(() => console.log('promise1 resolved')); // 1
Promise.resolve().then(() => console.log('promise2 resolved')); // 2
setTimeout(() => {
console.log('set timeout3') // 7
Promise.resolve().then(() => console.log('inner promise3 resolved')); // 8
}, 0);
setTimeout(() => console.log('set timeout1'), 0); // 9
setTimeout(() => console.log('set timeout2'), 0); // 10
Promise.resolve().then(() => console.log('promise4 resolved')); // 3
Promise.resolve().then(() => {
console.log('promise5 resolved') // 4
Promise.resolve().then(() => console.log('inner promise6 resolved')); // 6
});
Promise.resolve().then(() => console.log('promise7 resolved')); // 5

解释上面的执行顺序:

  1. 每一个 setTimeout 都会放到下一轮宏任务中触发。
  2. 本轮微任务中产生的新的微任务,会被加到队尾,仍然在本轮微任务队列中执行完毕。解释了 inner promise6 resolved 在 promise7 resolved 后面
  3. 宏任务中产生的微任务,会在该轮宏任务结束之后,统一在微任务队列中执行,解释了 inner promise3 resolved 在 set timeout3 后面

于是得到下面的图,紫色代表宏任务,黄色代表微任务。

观察可以发现,在第一个宏任务中,除了创建 setTimeout 和 Promise 之外,是没执行什么同步代码的,现在我们在原先的代码最后再加一行:

...
Promise.resolve().then(() => console.log('promise7 resolved')); // 5
new Promise((reslove) => console.log('promise instance'));

此时,尽管代码被加在最后一行,promise instance 却会第一个打印出来,因为 new Promise 中传入的函数是立即执行的。也就是说在第一轮宏任务中执行的。

microtask 是會在每一輪 event loop 進行渲染之前會被觸發

且只要在 microtask queue 裡面還有東西的話,就會一直執行下去

直到整個 microtask queue 變成空的為止

也就是說在 microtask 執行的時候,又觸發 queue 新的 microtask 的話

這個新的 microtask 也是會在此輪 task 執行完之前執行,不會留到下一輪 task

Vue 的 nextTick

我们再看下宏任务,微任务,渲染的流程

浏览器的 eventloop 是宏任务(dom 更新)=> 微任务 => 渲染,而 Vue 的 DOM 操作是在宏任务中进行的。这就解释了为什么 Vue 在完成本轮 DOM 更新之后,$nextTick 在本轮的微任务中执行回调函数,就可以确保拿到最新 DOM,尽管此时页面还没有将最新的 DOM 渲染到浏览器上。打开开发者工具,依次执行下面代码可以证明这一点:

// 模拟上一轮更新状态
document.body.style.background = 'yellow';

接下来模拟执行本轮更新操作:

document.body.style.background = 'red';
Promise.resolve().then(() =>{
let start = Date.now();
while(Date.now() - start < 3000) {
console.log(document.body.style.background) // 由于 while 循环,这里拿到的 background 已经是最新的 red,但是页面显示还是黄色
}
});
// 微任务执行结束之后,页面变成红色

上面的例子说明,在微任务中就可以拿到 Vue 本轮更新的DOM,而此时页面渲染的未必是最新的DOM。本文完。

参考资料:

https://yu-jack.github.io/2020/02/03/javascript-runtime-event-loop-browser/

https://juejin.im/post/6844903919789801486#heading-4

https://blog.insiderattack.net/javascript-event-loop-vs-node-js-event-loop-aea2b1b85f5c

event loop整理的更多相关文章

  1. javascript运行模式:并发模型 与Event Loop

    看了阮一峰老师的JavaScript 运行机制详解:再谈Event Loop和[朴灵评注]的文章,查阅网上相关资料,把自己对javascript运行模式和EVENT loop的理解整理下,不一定对,日 ...

  2. 浏览器与Node的事件循环(Event Loop)有何区别?

    前言 本文我们将会介绍 JS 实现异步的原理,并且了解了在浏览器和 Node 中 Event Loop 其实是不相同的. 一.线程与进程 1. 概念 我们经常说 JS 是单线程执行的,指的是一个进程里 ...

  3. js的事件循环(Event Loop)

    (本文从掘金小册整理) 首先介绍一下几个概念 进程与线程 相信大家经常会听到 JS 是单线程执行的,但是你是否疑惑过什么是线程? 讲到线程,那么肯定也得说一下进程.本质上来说,两个名词都是 CPU 工 ...

  4. Node.js event loop 和 JS 浏览器环境下的事件循环的区别

    Node.js  event loop 和 JS 浏览器环境下的事件循环的区别: 1.线程与进程: JS 是单线程执行的,指的是一个进程里只有一个主线程,那到底什么是线程?什么是进程? 进程是 CPU ...

  5. [转]Event loop——浏览器和Node区别

    最近对Event loop比较感兴趣,所以了解了一下.但是发现整个Event loop尽管有很多篇文章,但是没有一篇可以看完就对它所有内容都了解的文章.大部分的文章都只阐述了浏览器或者Node二者之一 ...

  6. Atitit 解决Unhandled event loop exception错误的办法

    Atitit 解决Unhandled event loop exception错误的办法 查看workspace/.metadata/.log org.eclipse.swt.SWTError: No ...

  7. [译]Node.js - Event Loop

    介绍 在读这篇博客之前,我强列建议先阅读我的前两篇文章: Getting Started With Node.js Node.js - Modules 在这篇文章中,我们将学习 Node.js 中的事 ...

  8. Eclipse经常报Unhandled event loop exception的原因

    在公司的电脑上,Eclipse经常报Unhandled event loop exception 错误,非常频繁,通过搜索发现是因为电脑上安装了百度杀毒导致的.... 无语 另外 teamviewer ...

  9. Node.js 事件循环(Event Loop)介绍

    Node.js 事件循环(Event Loop)介绍 JavaScript是一种单线程运行但又绝不会阻塞的语言,其实现非阻塞的关键是“事件循环”和“回调机制”.Node.js在JavaScript的基 ...

随机推荐

  1. Java中详述线程间协作

    线程协作 首先引入一段代码: package 线程间数据共享; import java.util.Date; public class Watch { private static String ti ...

  2. js实现encodeHTML和decodeHTML

    HTML编解码说明/在线HTML编解码 encodeHTMl const encodeHTML = (html: string): string => { const div = documen ...

  3. LCA算法——倍增

    概况 LCA(Lowest Common Ancestors),即最近公共祖先,是指在有根树中,找出某两个结点u和v最近的公共祖先. 实现过程 预处理:通过dfs遍历,记录每个节点到根节点的距离dis ...

  4. vue-cli3移动端自适应配置 Vant组件库

    module.exports = { presets: [ '@vue/app' ], plugins: [ ['import', { libraryName: 'vant', libraryDire ...

  5. 2019牛客暑期多校训练营(第一场)H XOR(线性基)

    题意:给你n个数字,然后让你求所有满足异或和为0的子集的大小之和. 先对n个数求线性基,设线性基大小为r,可以分别计算线性基内数的贡献和线性基外数的贡献 1.线性基外:共n-r个数,枚举每个数x,将线 ...

  6. 关于数位DP的学习

    ---恢复内容开始--- 因为最近做比赛经常会出现数位DP,便尝试着去学学看数位DP. 先给出两篇论文的链接: <数位计数问题解法研究> <浅谈数位类统计问题> 然后也是寻找了 ...

  7. 【洛谷 p3373】模板-线段树 2(数据结构--线段树)

    题意:已知一个数列,你需要进行下面三种操作:1.将某区间每一个数加上x:2.将某区间每一个数乘上x:3.求出某区间每一个数的和. 解法:(唉 :-(,这题卡住我了......)对于加法和乘法的混合操作 ...

  8. HDOJ1232 畅通工程 DFS

    很早之前就做过的题以前用并查集做的 现在用DFS重做算是熟悉DFS吧 #include<stdio.h>#include<string.h>const int size=100 ...

  9. zjnuSAVEZ (字符串hash)

    Description There are eight planets and one planetoid in the Solar system. It is not a well known fa ...

  10. Educational Codeforces Round 87 (Rated for Div. 2) D树状数组加二分删除的值

    Sample Input 5 4 1 2 3 4 5 -5 -1 -3 -1 Sample Output 3 思路,首先发现a[i]的值的范围是在1~n之间,每次插入我们可以直接把cnt[a[i]]+ ...