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

目录
  • 前言
  • 引言
  • 为什么会有事件循环?
    • JS是单线程的
    • 同步任务和异步任务
  • JS事件循环
  • 宏任务与微任务
    • 常见的宏任务有哪些?
    • 常见的微任务有哪些?
  • 执行过程总结(重点)
    • 同步任务 —> 微任务 —> 宏任务...
  • 案例挑战
    • 案例1:
    • 案例2:
    • 案例3:

前言

相信对于刚学习JavaScript的新手来说,去理解JS中的事件循环原理以及异步执行过程比较困难,但是这是JS必须要会的基础知识,逃避不能解决问题,笔者曾经也被这个知识点困扰过,现根据以往的经验编写此文章,旨在帮助大家彻底搞懂它们以及自我巩固,话不多说,进入正题。

注意:本篇文章主要是基于浏览器环境,Node环境没有研究过暂不讨论

引言

我们先来小试牛刀,看看下面这段代码是怎么执行的,例1:

  setTimeout(() => {
    console.log('time')
  });
  new Promise((resolve, reject) => {
    console.log('p1');
    resolve();
  }).then(() => {
    console.log('res')
  });
  console.log(1);
 // 输出: p1 1 res time

怎么样?你想的输出结果和实际的输出结果是一样的吗?如果是一样的说明你对事件循环有一定的了解,但是你真的已经清楚的知道了事件循环的原理吗?让我们继续往下看。

为什么会有事件循环?

JS是单线程的

众所周知:JavaScript 是一门单线程语言,也就是说,同一个时间只能做一件事。这是因为 Javascript 这门脚 本语言诞生的使命所致——JavaScript 是为处理页面中用户的交互,以及操作 DOM 而诞生的。比如我们对 某个 DOM 元素进行添加和删除操作,不能同时进行。 应该先进行添加,之后再删除。

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。这样所导致的问题是: 如果 JS 执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉

为了解决这个问题,利用多核 CPU 的计算能力,HTML5 提出 Web Worker 标准,允许JavaScript 脚本创建多个线程,但是子线程完全受主线程控制。于是,JS 中出现了同步任务和异步任务。

同步任务和异步任务

  • 同步任务:

同步任务都在主线程上执行,形成一个执行栈。在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;

  • 异步任务:

不进入主线程、而进入”任务队列”的任务,当主线程中的任务运行完了,才会从”任务队列”取出异步任务放入主线程执行。JS 的异步是通过回调函数实现的。异步任务相关回调函数添加到任务队列中(任务队列也称为消息队列)

注意:异步任务执行机制在这里描述的比较笼统,主要方便大家理解,具体细节在后面的“宏任务与微任务”中会详细介绍

JS的事件循环就是基于同步任务与异步任务来展开的,让我们继续往下看:

JS事件循环

事件循环是JavaScript实现异步的一种方法,也是JavaScript的执行机制

如图:

当一个脚本第一次执行的时候,js引擎会解析这段代码,并将其中的同步代码按照执行顺序加入执行栈中,然后从头开始执行。

当遇到异步任务时不会一直等待事件的返回结果,而是将事件挂起(即交给其他线程处理,上图是指Web Worker),继续执行执行栈中的其他任务。

当异步事件返回结果时,js将异步事件callback函数放入队列中,被放入队列中的异步事件不会立即回调,等到当前执行栈中的任务都执行完成,处于闲置状态的主线程按照队列顺序将处于首位事件的callback函数放入执行栈中,执行该函数的同步代码,如果遇到了异步事件,同样也会将其回调函数放入事件队列中…

如此反复,就形成了一个循环,这也是被称为“事件循环(EventLoop)”的原因。

js事件循环的基本原理已经描述清楚,但是异步任务之间也有所不同:

任务队列实际上分为两个:宏任务队列和微任务队列。上图只表示了一个是为了便于大家理解事件循环,下面就是事件循环更细节的东西了

宏任务与微任务

上面讲到,js在执行异步任务时,回调函数会被放在js的任务队列中,实际上,回调函数的类别不同,执行的优先级也不同。

不同的优先级被分为两类,一类是宏任务(Micro task),一类是微任务(Macro task)。

回调函数是微任务时,会被放在微任务队列,回调函数是宏任务时,会被放在宏任务队列。

微任务的优先级高于宏任务,当主线程的任务执行完成时,会首先去执行微任务队列中首位的回调函数,当微任务队列中为空时,才回去执行宏任务队列中的回调函数。

常见的宏任务有哪些?

  • 包括整体代码 script
  • setTimeout()
  • setInterval()
  • setImmediate()(Node独有)
  • I/O
  • UI 交互事件(浏览器独有)
  • requestAnimationFrame() (浏览器独有)

常见的微任务有哪些?

  • Promise.then(); Promise.cath()
  • async/await
  • process.nextTick() (Node独有)
  • MutationObserver() (H5新增,监听DOM树变化)
  • Object.observe() (异步监视对象修改,已废弃)

注意:new Promise()属于同步任务,但是Promise.then(); Promise.cath()属于异步任务的微任务

执行过程总结(重点)

现在我们对事件循环有了深入了解了,但是它们的执行过程还不是很清晰,我们再把执行过程弄清楚了以后就能游刃有余了。

同步任务 —> 微任务 —> 宏任务...

  • 先执行所有同步任务,碰到异步任务放到任务队列中
  • 同步任务执行完毕,开始执行当前所有的异步任务
  • 先执行任务队列里面所有的微任务,如果执行过程中又产生了微任务也会在本次执行过程中执行(即在下一个宏任务执行之前执行,可以看看案例1)
  • 然后执行一个宏任务(从宏任务队列头部pop出一个宏任务进执行栈,该任务中的具体代码也如步骤1执行)
  • 然后再执行所有的微任务(此时的微任务一般为步骤4中产生出的微任务)
  • 再执行一个宏任务,再执行所有的微任务·······依次类推到执行结束。

3-6的这个循环称为事件循环Event Loop

案例挑战

学会了吗?让我们来做几个案例巩固一下吧

案例1:

const promise = new Promise((resolve, reject) => {
    resolve("10")
  }).then(res => {
    console.log("res1:", res)    //res1: hahaha
    return 9
  }).then(res => {
    console.log("res2:", res)    //res2: 9
    return 8
  }).then(res => {
    console.log("res3:", res)    //res3: 8
    let promise2=new Promise((resolve,reject)=>{
        resolve("p2")
      }).then(res=>{
        console.log(res)
        setTimeout(function(){
          console.log("setTimeout2")
        },0)
      })
  })
  console.log('aaa')
  setTimeout(function(){
    console.log("setTimeout1")
  },0)
  const promise1 = new Promise((resolve, reject) => {
    console.log("p1")
    resolve(989)
}).then(res => {
    console.log(res)
    return 990
}).then(res=>{
  console.log(res)
  return 991
}).then(res=>{
  console.log(res)
  return 0
})
/*输出结果:
aaa
p1
res1: 10
989
res2: 9
990
res3: 8
991
p2
setTimeout1
setTimeout2
*/

案例2:

console.log('1');
// 定义注解 setTimeout_1 用于下文使用方便
setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})
// setTimeout_2
setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})
// 输出结果:  1 7 6 8 2 4 3 5 9 11 10 12

案例3:

console.log('1');
setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})
setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})
// 输出结果:1 7 6 8 2 4 3 5 9 11 10 12

以上就是JavaScript事件循环剖析宏任务与微任务的详细内容,更多关于JavaScript 事件循环宏任务微任务的资料请关注我们其它相关文章!

(0)

相关推荐

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

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

  • 浅谈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事件环微任务和宏任务队列原理

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

  • 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(()=>{

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

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

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

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

  • 浅谈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执

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

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

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

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

  • 详解JavaScript事件循环机制

    众所周知,JavaScript 是一门单线程语言,虽然在 html5 中提出了 Web-Worker ,但这并未改变 JavaScript 是单线程这一核心.可看HTML规范中的这段话: To coordinate events, user interaction, scripts, rendering, networking, and so forth, user agents must use event loops as described in this section. There a

  • 深入分析JavaScript 事件循环(Event Loop)

    事件循环(Event Loop),是每个JS开发者都会接触到的概念,但是刚接触时可能会存在各种疑惑. 众所周知,JS是单线程的,即同一时间只能运行一个任务.一般情况下这不会引发问题,但是如果我们有一个耗时较多的任务,我们必须等该任务执行完毕才能进入下一个任务,然而等待的这段时间常常让我们无法忍受,因为我们这段时间什么都不能做,包括页面也是锁死状态. 好在,时代在进步,浏览器向我们提供了JS引擎不具备的特性:Web API.Web API包括DOM API.定时器.HTTP请求等特性,可以帮助我们

  • 在实例中重学JavaScript事件循环

    单线程的JS 众所周知js是一门单线程语言,即同一时间只能做一件事.为什么js是单线程的呢,主要与它的用途有关. 作为浏览器脚本语言,js的主要用途是和用户互动&操作DOM,我们并不想并行的操作DOM.如果不是单线程的话,我们一个线程在给DOM节点上添加内容,另一个线程却删除了这个节点,到底该以哪个为准呢? 所以为了避免复杂性,从一诞生,JavaScript 就是单线程. 事件循环(event loop) JS是一门单线程语言,意味着代码要一行一行的执行.所有任务都要排队,前一个任务结束,才会执

  • 一篇文章让你搞清楚JavaScript事件循环

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

  • JavaScript事件循环同步任务与异步任务

    目录 前言 执行栈与任务队列 执行栈 任务队列 同步任务与异步任务 同步任务 异步任务 js的执行机制 结语 前言 首先,在学习js中同步异步的问题前,需要明白,js是单线程的,为什么它得是单线程的呢?这得从它的使用场景来看了,它主要是用来让用户与页面进行交互的吧.那么假设js是多线程的,在这个线程里面,用户点击某个按钮会增加一个DOM节点,在另一个线程里面,用户点击这个按钮又会删除一个DOM节点,那么此时js就不知道该听谁的了.那同步异步的出现又是为了什么呢?假设没有异步,那么我们在向服务器请

  • javascript事件循环event loop的简单模型解释与应用分析

    本文实例讲述了javascript事件循环event loop的简单模型解释与应用.分享给大家供大家参考,具体如下: js是单线程的,但是event loop的出现,使得js拥有可以处理高并发的性能.那么怎么理解event loop呢?网上百度一堆文章,什么heap,stack,micro queue,macro queue,让初学者直接懵掉.这里采用很通俗的理解方式介绍下event loop. event loop顾名思义是事件循环,既然是循环,那循环的是什么呢? 对于一个js文件, 1,执行

  • JS面试之对事件循环的理解

    目录 一.是什么 事件循环(Event Loop) 二.宏任务与微任务 微任务 宏任务 三.async与await async await 四.流程分析 一.是什么 JavaScript 在设计之初便是单线程,即指程序运行时,只有一个线程存在,同一时间只能做一件事 为什么要这么设计,跟JavaScript的应用场景有关 JavaScript 初期作为一门浏览器脚本语言,通常用于操作 DOM ,如果是多线程,一个线程进行了删除 DOM ,另一个添加 DOM,此时浏览器该如何处理? 为了解决单线程运

随机推荐