zoukankan      html  css  js  c++  java
  • 事件循环 EventLoop(Promise,setTimeOut,async/await执行顺序)

    什么是事件循环?想要了解什么是事件循环就要从js的工作原理开始说起:

    JS主要的特点就是单线程,所谓单线程就是进程中只有一个线程在运行。

    为什么JS是单线程的而不是多线程的呢?

    JS的主要用途就是与用户交互,操作DOM,假设JS同时有两个线程,一个线程中在某个DOM节点上添加或者修改内容,而另一个线程在这个DOM节点上执行删除该节点操作,这样就会产生冲突。

    单线程就意味着所有任务都需要排队,前一任务结束,才会执行后一个任务,当是如果当遇到前一个任务耗时很长的情况,后一个任务就不得不一直等着。因此,就有了同步任务、异步任务。

    同步任务和异步任务在js中是如何执行的呢?

    js的代码运行会形成一个主线程和一个任务队列。主线程会自上而下依次执行我们的js代码,形成一个执行栈。

    同步任务就会被放到这个主线程中依次执行。而异步任务被放入到任务队列中执行,执行完就会在任务队列中打一个标记,形成一个对应的事件。当主线程中的任务全部运行完毕,js会去提取并执行任务队列中的事件。这个过程是循环进行的,这就是EventLoop。

    JS引擎执行异步代码不用等待,是因为有事件队列和事件循环。

    事件循环是指主线程重复从事件队列中取消息、执行的过程。指整个执行流程。

    事件队列是一个存储着待执行任务的序列,其中的任务严格按照时间先后顺序执行,排在队头的任务会率先执行,而排在队尾的任务会最后执行。(即先进先出)

    事件队列:

    • 一个线程中,事件循环是唯一的,但是任务队列可以有多个;
    • 任务队列又分macro-task(宏任务)和micro-task(微任务);
    • macro-task包括:script(整体代码)、setTimeout、setInterval、setImmediate、I/O、UI rendering;
    • micro-task包括:process.nextTick, Promise, Object.observe(已废弃), MutationObserver(html5新特性)
    • setTimeout/Promise等称为任务源,而进入任务队列的是他们制定的具体执行任务;来自不同任务源的任务会进入到不同的任务队列,其中setTimeout与setInterval是同源的。
     

    事件循环运行机制

    (1)执行一个宏任务(栈中没有就从事件队列中获取)

    (2)执行过程中如果遇到微任务,就将它添加到微任务的任务队列中;

    (3)宏任务执行完毕后,立即执行当前微任务队列的所有微任务;

    (4)当前微任务执行完毕,开始检查渲染,然后GUI线程接管渲染;

    (5)渲染完毕后,JS线程继续接管,开始下一个宏任务。

    事例:

                     async function async1() {           
                         console.log("async1 start");  //(2)        
                         await  async2();            
                         console.log("async1 end");   //(6)    
                     }        
                     async  function async2() {          
                         console.log( 'async2');   //(3)     
                     }       
                     console.log("script start");  //(1)      
                     setTimeout(function () {            
                         console.log("settimeout");  //(8)      
                     },0);        
                     async1();        
                     new Promise(function (resolve) {           
                         console.log("promise1");   //(4)         
                         resolve();        
                     }).then(function () {            
                         console.log("promise2");    //(7)    
                     });        
                     console.log('script end');//(5)

    按照事件循环机制分析以上代码运行流程:

      1. 首先,事件循环从宏任务(macrotask)队列开始,首先读取script(整体代码)任务;当遇到任务源(task source)时,则会先分发任务到对应的任务队列中去。

      2. 然后我们看到首先定义了两个async函数,此时没有调用,接着往下看,然后遇到了 `console` 语句,直接输出 `script start`。输出之后,script 任务继续往下执行,遇到 `setTimeout`,其作为一个宏任务源,则会先将其任务分发到对应的任务队列中。

      3. script 任务继续往下执行,执行了async1()函数,async函数中在await之前的代码是立即执行的,所以会立即输出`async1 start`。
    遇到了await时,会将await后面的表达式执行一遍,所以就紧接着输出`async2`,然后将await后面的代码也就是`console.log('async1 end')`加入到microtask中的Promise队列中,接着跳出async1函数来执行后面的代码。

      4. script任务继续往下执行,遇到Promise实例。由于Promise中的函数是立即执行的,而后续的 `.then` 则会被分发到 microtask 的 `Promise` 队列中去。所以会先输出 `promise1`,然后执行 `resolve`,将 `promise2` 分配到对应队列。

      5. script任务继续往下执行,输出了 `script end`,至此,全局任务就执行完毕了。
    根据上述,每次执行完一个宏任务之后,会去检查是否存在 Microtasks;如果有,则执行 Microtasks 直至清空 Microtask Queue。
    因而在script任务执行完毕之后,开始查找清空微任务队列。此时,微任务中, `Promise` 队列有的两个任务`async1 end`和`promise2`,因此按事件队列先进先出的原则,先后顺序输出 `async1 end,promise2`。当所有的 Microtasks 执行完毕之后,表示第一轮的循环就结束了。

      6. 第二轮循环依旧从宏任务队列开始。此时宏任务中只有一个 `setTimeout`,取出直接输出即可,至此整个流程结束。

  • 相关阅读:
    解决 Mac launchpad 启动台 Gitter 图标无法删除的问题
    React 与 React-Native 使用同一个 meteor 后台
    解决 React-Native mac 运行报错 error Failed to build iOS project. We ran "xcodebuild" command but it exited with error code 65. To debug build logs further, consider building your app with Xcode.app, by ope
    一行命令更新所有 npm 依赖包
    swift学习笔记
    IOS语言总结
    focusSNS学习笔记
    别小看锤子,老罗真的很认真
    windowsphone开发页面跳转到另一个dll中的页面
    【令人振奋】【转】微软潘正磊谈DevOps、Visual Studio 2013新功能、.NET未来
  • 原文地址:https://www.cnblogs.com/xuelanying/p/14406912.html
Copyright © 2011-2022 走看看