当前位置 : 主页 > 网络编程 > JavaScript >

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

来源:互联网 收集:自由互联 发布时间:2023-01-30
目录 前言 引言 为什么会有事件循环? JS是单线程的 同步任务和异步任务 JS事件循环 宏任务与微任务 常见的宏任务有哪些? 常见的微任务有哪些? 执行过程总结(重点) 同步任务
目录
  • 前言
  • 引言
  • 为什么会有事件循环?
    • 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 事件循环宏任务微任务的资料请关注自由互联其它相关文章!

    上一篇:自己写一个uniapp全局弹窗(APP端)
    下一篇:没有了
    网友评论