当控制台遇上异步数据

143 阅读3分钟

当控制台遇上异步数据

  • 同步和异步

    • 首先我们来回顾一下JS的同步和异步的执行逻辑

      • JS利用事件循环规定了同步和异步的执行逻辑

        1. 执行栈和事件队列

          • 执行栈:当主线程在执行过程中遇到同步代码,会按照顺序添加到执行栈。

            function a(){
              b();
              console.log('a');
            }
            function b(){
              console.log('b');
            }
            a()
            

            上面的实例代码中,执行栈的变化如下

            1. 首先要执行函数a,所以函数a入栈
            2. 函数a中首先要执行函数b,所以函数b入栈
            3. 执行函数b,console.log('b')入栈
            4. console.log('b')执行完毕,出栈
            5. 函数b执行完成,出栈
            6. console.log('a')入栈
            7. console.log('a')执行完,出栈
            8. 函数a出栈
          • 事件队列:当主线程在执行过程中遇到异步代码,主线程不会等待异步代码执行,而是会将异步代码挂起,当异步代码(如,网络请求)得到结果之后,会将它的回调放入事件队列,等待主线程清空执行栈之后,主线程会去事件队列中查找是否存在任务,如果存在,那么主线程将从事件队列队头取出任务,将它的回调放入执行栈,主线程会开始执行它的同步代码

            注意实际上,不同的异步任务将被放入不同的异步任务队列(宏任务队列和微任务队列)

            function a(){
              b();
              console.log('a');
            }
            function b(){
              console.log('b');
              setTimeout(function() {
                console.log('c');
              }, 2000)
            }
            a()
            
        2. 宏任务和微任务

          • 什么是宏任务和微任务
            • 异步任务被分为2种:宏任务和微任务,实际上它们俩都是异步任务。
          • 为什么要把异步任务划分为2种
            • 因为异步任务实际上是存在优先级的,如果所有异步任务都是一种,那么主线程将按顺序从队头取出任务放到执行栈执行。
          • 有哪些宏任务
            • script(整体代码)
            • setTimeout()
            • setInterval()
            • postMessage
            • I/O
            • UI交互事件
          • 有哪些微任务
            • Promise().then中的回调
            • MutationObserver
        3. 一个(浏览器)事件循环的过程(执行一个宏任务,并清空所有微任务)

          1. 第一步,主线程去宏任务队列中取出一个宏任务放入执行栈,直到清空宏任务队列

            (在这个过程中,如果主线程遇到异步代码,那么会等待它们得到结果后按照分类放入不同的异步队列)

          2. 第二步,当这个宏任务执行完毕,出栈,主线程会去查看微任务队列是否存在任务,如果存在微任务,那么主线程将清空微任务队列

  • 当想使用console.log打印异步数据

    • 一个例子

      let obj = {
        a: 1,
      }
      console.log(obj)
      obj.a++
      

      在上面的例子中,我们直觉的认为,打印出的obj中的a是1,但是实际上 截屏2022-03-03 下午4.12.20.png

    • 导致问题的原因

      • 上面例子的执行逻辑
        1. 当主线程执行到console.log(obj),浏览器会立即使用JSON.stringify,将对象序列化为一个字符串来展示
        2. 主线程会继续执行,当执行到obj.a++,所以这时对象obj中的a属性的值变为2
        3. 当我们去点开obj这个打印内容,这时浏览器就会去对象obj的地址读取当前a属性的值,所以这时就变成了2