宏任务和微任务

让我们从浏览器加载 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. DOM分类及HTML DOM

    DOM简介 DOM是W3C(World Wide Web Consortium)标准. "W3C 文档对象模型(DOM,全称Document Object Model)"是一个使程 ...

  2. The Department of Redundancy Department

    Write a program that will remove all duplicates from a sequence of integers and print the list of un ...

  3. 3. Linear Regression with Multiple Variables

    前面还有一章主要讲解,基本的Linear Algebra线性代数的知识,都比较简单,这里就直接跳过了. Speaker: Andrew Ng 1. Multiple featues 训练集的特征变成了 ...

  4. 【noi 2.6_90】滑雪(DP)

    题意:输出最长下降路径的长度. 解法:f[i][j]表示结尾于(i,j)的最长的长度.由于无法确定4个方位已修改到最佳,所以用递归实现. 1 #include<cstdio> 2 #inc ...

  5. 最新版gradle安装使用简介

    目录 简介 安装gradle和解决gradle安装的问题 Gradle特性 标准task Build phases Gradle Wrapper wrapper的使用 wrapper的升级 一个简单的 ...

  6. CF1415-C. Bouncing Ball

    CF1415-C. Bouncing Ball 题意: 在\(x\)轴上有\(n\)个点(从\(1\)到\(n\)),每个点都有一个值\(0\)或\(1\),\(0\)代表这个点不能走,\(1\)代表 ...

  7. 力扣567.字符串的排列—C语言实现

    题目 来源:力扣(LeetCode)

  8. Linux系统诊断必备技能之二:日志查询常用命令详解

    一.概述 日常运维工作中,排查线上环境问题,少不了去线上查日志.而使用什么命令,能快速准确地查到我们需要查找地日志信息,也是我们需要掌握的一项技能.下面介绍一下日常工作常用到的查看日志命令:tail, ...

  9. woj1005-holding animals-01pack woj1006-Language of animals-BFS

    title: woj1005-holding animals-01pack date: 2020-03-05 categories: acm tags: [acm,woj,pack] 01背包.中等题 ...

  10. C++:Process returned -1073741571 (0xC00000FD)

    启动程序无法输入,然后崩溃报错Process returned -1073741571 (0xC00000FD) 原因: 栈溢出了 栈的默认内存空间为1M,如果函数中定义的数组太大会导致内存溢出. 解 ...