JS事件循环-微任务-宏任务(原理讲解+面试题分析)

目录
  • 前言
  • 浏览器的事件循环
    • 浏览器的事件循环
    • 浏览器的宏任务、微任务
    • 面试题一
    • 面试题二
  • node的事件循环
    • node的事件循环
    • node的宏任务、微任务
    • 面试题一

前言

JS代码在运行时,有两种运行环境。

一是在浏览器中,二是在node中。

由于JS线程是单线程,在运行JS代码时,可能会遇到比较耗时的操作,比如setTimeout,或者是发送网络请求等,又由于JS线程是单线程,如果在解析耗时的代码时候,停在了这里,那执行代码的性能将是比较低的。

为了解决此问题,在浏览器、node环境下,其实是有事件循环机制的。

浏览器的事件循环

浏览器的事件循环

JS线程执行代码时候,遇到比较耗时的操作时,将这些操作交给浏览器去处理,然后这些操作会根据不同的种类放进微任务队列或者宏任务队列,宏任务队列和微任务队列都不为空的时候,只有等微任务队列为空,即微任务队列里面的事件全部都执行完之后,才会再去让宏任务队列中的事件出栈,之后交由JS线程去处理,执行代码。

事件循环大概就是如图所示的流程:

浏览器的宏任务、微任务

其实,在浏览器拿到那些有些不能同步处理的事件的时候,有的会加入宏任务队列,有的会加入微任务队列,那么一般我们如何区分呢?

一般情况下:加入宏任务队列和微任务队列的事件如下:

宏任务队列(macrotask queue):ajax、setTimeout、setInterval、DOM监听、UI Rendering等

微任务队列(microtask queue):Promise的then回调、 Mutation Observer API、queueMicrotask()。

那么这些事件的执行顺序是怎么样子的呢?

首先,有一个原则,宏任务队列里面的事件,要执行的话,一定是在确保微任务队列为空的情况下,即微任务队列里面的事件全部执行完的情况。

其次,main script里面的内容是最先执行的。

由此,可以得到执行顺序为:main script > 微任务队列里面的事件 > 宏任务里面的事件。

面试题一

题目如下:

setTimeout(function () {
  console.log("setTimeout1");
  new Promise(function (resolve) {
    resolve();
  }).then(function () {
    new Promise(function (resolve) {
      resolve();
    }).then(function () {
      console.log("then4");
    });
    console.log("then2");
  });
});

new Promise(function (resolve) {
  console.log("promise1");
  resolve();
}).then(function () {
  console.log("then1");
});

setTimeout(function () {
  console.log("setTimeout2");
});

console.log(2);

queueMicrotask(() => {
  console.log("queueMicrotask1")
});

new Promise(function (resolve) {
  resolve();
}).then(function () {
  console.log("then3");
});

// promise1
// 2
// then1
// queueMicrotask1
// then3
// setTimeout1
// then2
// then4
// setTimeout2

分析如下:

在第一个事件循环里面,main script、宏任务、微任务里面的事件如下:

在判断加入宏任务队列还是微任务队列时候,遵循如下原则:

宏任务队列(macrotask queue):ajax、setTimeout、setInterval、DOM监听、UI Rendering等
微任务队列(microtask queue):Promise的then回调、 Mutation Observer
API、queueMicrotask()。

按照这个原则,第一轮事件循环里面的事件如下:

先执行main script、然后微任务队列里面的,最后是宏任务队列里面的

// promise1
// 2
// then1
// queueMicrotask1
// then3

之后执行setTimeout1的宏任务,此时第二轮事件循环里面的内容如下:

第二轮事件循环执行内容如下:

// setTimeout1
// then2
// then4
// setTimeout2

综上:最后执行结果为:

// promise1
// 2
// then1
// queueMicrotask1
// then3
// setTimeout1
// then2
// then4
// setTimeout2

面试题二

题目如下:

// async function bar() {
//   console.log("22222")
//   return new Promise((resolve) => {
//     resolve()
//   })
// }

// async function foo() {
//   console.log("111111")

//   await bar()

//   console.log("33333")
// }

// foo()
// console.log("444444")

async function async1 () {
  console.log('async1 start')
  await async2();
  console.log('async1 end')
}

async function async2 () {
  console.log('async2')
}

console.log('script start')

setTimeout(function () {
  console.log('setTimeout')
}, 0)

async1();

new Promise (function (resolve) {
  console.log('promise1')
  resolve();
}).then (function () {
  console.log('promise2')
})

console.log('script end')

// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout

第一轮事件循环里面的事件如下:

然后按照顺序执行,最后结果如下:

// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout

node的事件循环

node的事件循环

浏览器的事件循环是是根据HTML5定义的规范来实现的,不同的浏览器可能会有不同的实现,而Node中是由libuv实现的。

首先我们看一下node的架构图:

我们可以从图中大致看出,事件循环是在libuv中实现的,libuv主要维护的是一个事件循环(Event Loop)和 线程池(worker threads)。

libuv是一个多平台的专注于异步IO的库,它最初是为Node开发的,但是现在也被使用到Luvit、Julia、pyuv等其他地方;

EventLoop负责调用系统的一些其他操作:文件的IO、Network、child-processes等

由图可以看出,事件循环就像是一个桥梁,是连接着应用程序的JavaScript(左边部分)和系统调用(右边线程池部分)之间的通道:

无论是我们的文件IO、数据库、网络IO、定时器、子进程,在完成对应的操作后,都会将对应的结果和回调函数放到事件循环(任务队列)中;

事件循环会不断的从任务队列中取出对应的事件(回调函数)来执行;

但是一次完整的事件循环Tick分成很多个阶段:

  1. 定时器(Timers):本阶段执行已经被 setTimeout() 和 setInterval() 的调度回调函数。
  2. 待定回调(Pending Callback):对某些系统操作(如TCP错误类型)执行回调,比如TCP连接时接收到ECONNREFUSED。idle, prepare:仅系统内部使用。
  3. 轮询(Poll):检索新的 I/O 事件;执行与 I/O 相关的回调;
  4. 检测(check):setImmediate() 回调函数在这里执行。
  5. 关闭的回调函数:一些关闭的回调函数,如:socket.on(‘close’, …)

node的宏任务、微任务

node中也有微任务和宏任务,执行的原则和在浏览器中一样,是先执行微任务,然后再执行宏任务,但是对于宏任务来说,是按照上图从上到下的顺序执行的。

具体对应的常见事件的执行顺序如下;

在微任务队列中:

  • next tick queue:process.nextTick;
  • other queue:Promise的then回调、queueMicrotask;

(是按照从上往下的事件顺序执行)

在宏任务队列:

  • timer queue:setTimeout、setInterval;
  • poll queue:IO事件;
  • check queue:setImmediate;
  • close queue:close事件

(同样是按照从上往下的事件顺序执行)

所以,综上所述,在每一次事件循环的tick中,会按照如下顺序来执行代码:

next tick microtask queue;
other microtask queue;
timer queue;
poll queue;
check queue;
close queue

当然,main script 依旧是最先执行的,只有main script执行结束后,才会按照上述顺序来执行代码。

面试题一

async function async1() {
  console.log('async1 start')
  await async2()
  console.log('async1 end')
}

async function async2() {
  console.log('async2')
}

console.log('script start')

setTimeout(function () {
  console.log('setTimeout0')
}, 0)

setTimeout(function () {
  console.log('setTimeout2')
}, 300)

setImmediate(() => console.log('setImmediate'));

process.nextTick(() => console.log('nextTick1'));

async1();

process.nextTick(() => console.log('nextTick2'));

new Promise(function (resolve) {
  console.log('promise1')
  resolve();
  console.log('promise2')
}).then(function () {
  console.log('promise3')
})

console.log('script end')

// script start
// async1 start
// async2
// promise1
// promise2
// script end
// nexttick1
// nexttick2
// async1 end
// promise3
// settimetout0
// setImmediate
// setTimeout2

第一轮事件循环里面的事件如下:

按照顺序自左向右执行,3s后执行setTimeout2,

最后的结果是:

// script start
// async1 start
// async2
// promise1
// promise2
// script end
// nexttick1
// nexttick2
// async1 end
// promise3
// settimetout0
// setImmediate
// setTimeout2

到此这篇关于JS事件循环-微任务-宏任务(原理讲解+面试题分析)的文章就介绍到这了,更多相关循环-微任务-宏任务内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • JavaScript事件循环剖析宏任务与微任务

    目录 前言 引言 为什么会有事件循环? JS是单线程的 同步任务和异步任务 JS事件循环 宏任务与微任务 常见的宏任务有哪些? 常见的微任务有哪些? 执行过程总结(重点) 同步任务 —> 微任务 —> 宏任务... 案例挑战 案例1: 案例2: 案例3: 前言 相信对于刚学习JavaScript的新手来说,去理解JS中的事件循环原理以及异步执行过程比较困难,但是这是JS必须要会的基础知识,逃避不能解决问题,笔者曾经也被这个知识点困扰过,现根据以往的经验编写此文章,旨在帮助大家彻底搞懂它们以及自

  • 浅谈javascript事件环微任务和宏任务队列原理

    JS 事件环 JS 程序的运行是离不开事件环机制的,这个机制保证在发生某些事情的时候我们有机会执行一个我们事先预定好的函数,事情发生的时候 JS 会将相应的函数入栈执行然后出栈,但是关于事件环我们还有一些未知的东西,例如,setTimeout 我们习惯称他为定时器,但是可能很多人没有意识到,这个东西和我们常用的一些事件没什么不同,只不过我们通常所说的事件大多需要用户触发,而 setTimeout 不用用户自己触发,而是指定时间之后触发:那么问题来了,如果我们将时间设置为 0 会发生什么?会立即执

  • JavaScript事件循环及宏任务微任务原理解析

    首先看一段代码: 打印顺序是什么? 正确答案:script start, script end, promise1, promise2, setTimeout 其中涉及到事件循环(event loop),宏任务(macrotask),微任务(microtask) 一.事件循环 Event Loop 程序中设置两个线程:一个负责程序本身的运行,称为"主线程":另一个负责主线程与其他进程(主要是各种I/O操作)的通信,被称为"Event Loop线程"(可以译为&quo

  • JavaScript中的宏任务和微任务详情

    目录 1.微任务有哪些 2.宏任务有哪些 3.案例 3.1 结论 4.代码案例  4.1 代码分析 4.2 结论和运用的场景 1.微任务有哪些 Promise await和async 2.宏任务有哪些 setTimeout setInterval DOM事件 AJAX请求 3.案例 <script> console.log(1) setTimeout(()=>{ console.log("2") },0) Promise.resolve().then(()=>{

  • JavaScript宏任务和微任务区别介绍

    目录 分类 宏任务 微任务 为什么 案例 分类 js中的任务,大致分为2类,一类是同步任务,另一类是异步任务.而异步任务,又分为宏任务和微任务,这两个任务是两个队列,所以是先进先出的. 同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务. 异步任务指的是,不进入主线程.而进入"任务队列"的任务,只有等主线程任务执行完毕,"任务队列"开始通知主线程,请求执行任务,该任务才会进入主线程执行 宏任务 主代码块.setTimeout.set

  • 浅谈js中的宏任务和微任务

    目录 1.关于JavaScript 2.JavaScript事件循环 3.宏任务和微任务 4.拓展宏任务微任务 下面一道关于宏任务和微任务的题: setTimeout(function(){ console.log('1') }); new Promise(function(resolve){ console.log('2'); resolve(); }).then(function(){ console.log('3') }); console.log('4') 试问一下上面代码的执行顺序是啥

  • 浅谈JavaScript宏任务和微任务执行顺序

    目录 一.JavaScript单线程 1. 同步任务(synchronous) 2. 异步任务(asynchronous) 二.任务队列(task queue) 1.执行栈 扩展一下setTimeout的理解 一.JavaScript单线程 JavaScript是单线程指的是同一时间只能干一件事情,只有前面的事情执行完,才能执行后面的事情.导致遇到耗时的任务时后面的代码无法执行. 在此之前啊 我们必须了解同步和异步 1. 同步任务(synchronous) console.log(123); c

  • JavaScript 微任务和宏任务讲解

    前言: js是一门单线程语言,所以它本身是不可能异步的,但是js的宿主环境(比如浏览器.node)是多线程,宿主环境通过某种方式(事件驱动)使得js具备了异步的属性.而在js中,我们一般将所有的任务都分成两类,一种是同步任务,另外一种是异步任务.而在异步任务中,又有着更加细致的分类,那就是微任务和宏任务 1.概念 1.1宏任务 宏任务 ---- setTimeout.setInterval.DOM事件.AJAX请求 浏览器为了能够使得JS内部task与DOM任务能够有序的执行,会在一个task执

  • JS事件循环-微任务-宏任务(原理讲解+面试题分析)

    目录 前言 浏览器的事件循环 浏览器的事件循环 浏览器的宏任务.微任务 面试题一 面试题二 node的事件循环 node的事件循环 node的宏任务.微任务 面试题一 前言 JS代码在运行时,有两种运行环境. 一是在浏览器中,二是在node中. 由于JS线程是单线程,在运行JS代码时,可能会遇到比较耗时的操作,比如setTimeout,或者是发送网络请求等,又由于JS线程是单线程,如果在解析耗时的代码时候,停在了这里,那执行代码的性能将是比较低的. 为了解决此问题,在浏览器.node环境下,其实

  • 详解JS事件循环及宏任务微任务的原理

    目录 宏任务 微任务 事件循环 宏任务与微任务 微任务中创建宏任务 宏任务中创建微任务 宏任务中创建宏任务 微任务中创建微任务 总结 本质上来说,JavaScript是同步的.阻塞的.单线程语言,不管是在浏览器中还是nodejs环境下.浏览器在执行js代码和渲染DOM节点都是在同一个线程中,执行js代码就无法渲染DOM,渲染DOM的时候就无法执行js代码.如果按照这种同步方式执行,页面的渲染将会出现白屏甚至是报错,特别是遇到一些耗时比较长的网络请求或者js代码,因此在实际开发中一般是通过异步的方

  • node.js事件循环机制及与js区别详解

    目录 一.是什么 二.流程 三.题目 一.是什么 在浏览器事件循环(opens new window)中,我们了解到javascript在浏览器中的事件循环机制,其是根据HTML5定义的规范来实现 而在NodeJS中,事件循环是基于libuv实现,libuv是一个多平台的专注于异步IO的库,如下图最右侧所示: 上图EVENT_QUEUE 给人看起来只有一个队列,但EventLoop存在6个阶段,每个阶段都有对应的一个先进先出的回调队列 二.流程 上节讲到事件循环分成了六个阶段,对应如下: tim

  • Node.js事件循环(Event Loop)和线程池详解

    Node的"事件循环"(Event Loop)是它能够处理大并发.高吞吐量的核心.这是最神奇的地方,据此Node.js基本上可以理解成"单线程",同时还允许在后台处理任意的操作.这篇文章将阐明事件循环是如何工作的,你也可以感受到它的神奇. 事件驱动编程 理解事件循环,首先要理解事件驱动编程(Event Driven Programming).它出现在1960年.如今,事件驱动编程在UI编程中大量使用.JavaScript的一个主要用途是与DOM交互,所以使用基于事件

  • 深入理解Node.js 事件循环和回调函数

    本文详细的介绍了Node.js 事件循环和Node.js回调函数,废话不多说了,具体看下面把.  一.Node.js 事件循环 Node.js 是单进程单线程应用程序,但是通过事件和回调支持并发,所以性能非常高.Node.js 的每一个 API 都是异步的,并作为一个独立线程运行,使用异步函数调用,并处理并发.Node.js 基本上所有的事件机制都是用设计模式中观察者模式实现.Node.js 单线程类似进入一个while(true)的事件循环,直到没有事件观察者退出,每个异步事件都生成一个事件观

  • Node.js 事件循环详解及实例

     Node.js  事件循环详解及实例 Node.js 是单进程单线程应用程序,但是通过事件和回调支持并发,所以性能非常高. Node.js 的每一个 API 都是异步的,并作为一个独立线程运行,使用异步函数调用,并处理并发. Node.js 基本上所有的事件机制都是用设计模式中观察者模式实现. Node.js 单线程类似进入一个while(true)的事件循环,直到没有事件观察者退出,每个异步事件都生成一个事件观察者,如果有事件发生就调用该回调函数. Node.js 有多个内置的事件,我们可以

  • 深入浅析Node.js 事件循环、定时器和process.nextTick()

    什么是事件循环 尽管JavaScript是单线程的,但通过尽可能将操作放到系统内核执行,事件循环允许Node.js执行非阻塞I/O操作. 由于现代大多数内核都是多线程的,因此它们可以处理在后台执行的多个操作. 当其中一个操作完成时,内核会告诉Node.js,以便可以将相应的回调添加到 轮询队列 中以最终执行. 我们将在本主题后面进一步详细解释. 事件循环解释 当Node.js启动时,它初始化事件循环,处理提供的输入脚本(或放入 REPL ,本文档未涉及),这可能会进行异步API调用,调度计时器或

  • 详解node.js 事件循环

    Node.js 是单进程单线程应用程序,但是因为 V8 引擎提供的异步执行回调接口,通过这些接口可以处理大量的并发,所以性能非常高. Node.js 几乎每一个 API 都是支持回调函数的. Node.js 基本上所有的事件机制都是用设计模式中观察者模式实现. Node.js 单线程类似进入一个while(true)的事件循环,直到没有事件观察者退出,每个异步事件都生成一个事件观察者,如果有事件发生就调用该回调函数. 事件驱动程序 Node.js 使用事件驱动模型,当web server接收到请

  • Node.js  事件循环详解及实例

     Node.js  事件循环详解及实例 Node.js 是单进程单线程应用程序,但是通过事件和回调支持并发,所以性能非常高. Node.js 的每一个 API 都是异步的,并作为一个独立线程运行,使用异步函数调用,并处理并发. Node.js 基本上所有的事件机制都是用设计模式中观察者模式实现. Node.js 单线程类似进入一个while(true)的事件循环,直到没有事件观察者退出,每个异步事件都生成一个事件观察者,如果有事件发生就调用该回调函数. Node.js 有多个内置的事件,我们可以

  • 实例分析js事件循环机制

    本文通过实例给大家详细分析了JS中事件循环机制的原理和用法,以下是全部内容: var start = new Date() setTimeout(function () { var end = new Date console.log('Time elapsed:', end - start, 'ms') }, 500) while (new Date() - start < 1000) { } 有其他语言能完成预期的功能吗?Java, 在Java.util.Timer中,对于定时任务的解决方案

随机推荐