宏任务和微任务

让我们从浏览器加载 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. 整理我的Git常见问题和命令

    整理我的Git常见问题和命令 目录 整理我的Git常见问题和命令 提交注释规范 合并分支 clone & 切换分支 支持中文路径显示 账户及密码 基于远程分支创建本地分支 提交注释规范 举例: ...

  2. js实现购物车左滑删除

    使用 js 和jquery动画实现购物车左滑,请引入jquery库,不然会报错哦! 首页编写页面,注意布局,滑动分成两部分,商品图片和信息放在一个布局,删除和移入收藏放在一起. 其中js用到了 tou ...

  3. Codeforces Global Round 9 B. Neighbor Grid

    题目链接:https://codeforces.com/contest/1375/problem/B 题意 给出一个 $n \times m$ 的方阵,每个方格中有一个非负整数,一个好方格定义如下: ...

  4. Codeforces Round #613 (Div. 2) B. Just Eat It!(前缀和)

    题意: 一个长为n的序列,是否存在与原序列不同的连续子序列,其元素之和大于等于原序列. 思路: 从前.后分别累加,若出现非正和,除此累加序列外的子序列元素之和一定大于等于原序列. #include & ...

  5. L2-013 红色警报 (25分) 并查集复杂度

    代码: 1 /* 2 这道题也是简单并查集,并查集复杂度: 3 空间复杂度为O(N),建立一个集合的时间复杂度为O(1),N次合并M查找的时间复杂度为O(M Alpha(N)), 4 这里Alpha是 ...

  6. IIS Web API 长时间不连接后第一次访问非常缓慢

    搭建在 IIS 上的 Web API 若长时间不访问,会出现第一次访问耗时较长的现象,这与其调用应用程序池的 Idle Time-out(minutes) 即闲置超时设置有关.默认值为20,修改为0即 ...

  7. 对于kmp求next数组的理解

    首先附上代码 1 void GetNext(char* p,int next[]) 2 { 3 int pLen = strlen(p); 4 next[0] = -1; 5 int k = -1; ...

  8. Java开发工程师最新面试题库系列——Web部分(附答案)

    WEB 如果你有更好的想法请在评论区留下您的答案,一起交流讨论 http和https有什么区别? 答:http是超文本传输协议,默认端口是80.https是安全的默认端口是443:http是明文传输, ...

  9. 记一次小米手机安装Google Play(其他手机类似)

    记一次小米手机安装Google Play(其他手机类似) 最近换了一款小米10青春版,性价比很高,对于开发者而言,手机自带商店的软件内容往往不能满足需求,而需要单独定制习惯性的APP,博主通过最近的尝 ...

  10. PPT online viewer

    PPT online viewer PPT 在线查看器 SpeakerDeck https://speakerdeck.com/xgqfrms/python?slide=3 SlideShare ht ...