JS:事件循环机制(Event Loops)

2021-04-06  本文已影响0人  limengzhe
  1. 在深入了解事件循环之前,我们不妨先做道题

    思考下面代码的执行结果

    setTimeout(() => {
      console.log(1)
    }, 0)
    
    new Promise((resolve, reject) => {
      console.log(2)
      resolve(3)
    }).then(val => {
      console.log(val)
    })
    
    console.log(4)
    

    输出结果是:

    2
    4
    3
    1
    

    你做对了吗?这里我们不禁要问:

    • 为什么 setTimeout() 设定的时间是 0 毫秒,但 1 却是在最后输出的?

    • 为什么 Promise.then() 的回调函数会在 4 输出之后执行,而不是在 2 之后?

    其实,在 JavaScript 中,代码的执行顺序并不是完全按照它们的书写顺序,而是取决于它们在事件循环中的顺序。

  2. 什么是事件循环?

    事件循环,即 Event Loops。用于协调事件、用户交互、JavaScript 脚本、DOM 渲染、网络请求等等的执行顺序问题。

    一个遵循 ECMAScript 标准的代理(浏览器或 JS 引擎)也必须遵循事件循环机制。

    事件循环是由一个或以上的任务队列组成的。

  3. 什么是任务队列?

    任务队列,即 Task Queues,是一组任务的集合(Sets)。

    由于 JavaScript 是 单线程 语言,所以在 JS 中所有的任务都需要排队执行,这些任务共同组成了任务队列,依次排队执行的过程,形成一个执行栈(Execution Context Stack)

    在任务队列中最先执行是同步任务。

  4. 什么是同步任务?

    同步任务,即 Synchronous Task。就是当上一个任务执行完成后,接下来可以立即执行的任务。它们在主线程上依次排队执行,直到清空。

    比如,下面代码中的 for()console.log() 将会依次执行,最终输出 0 1 2 done

    for (let i = 0; i < 3; i++) {
      console.log(i)
    }
    
    console.log('done')
    

    与同步任务相比,异步任务的执行充满了不确定性。

  5. 什么是异步任务?

    异步任务,即 Asynchronous Task。就是需要等待被通知才以执行的任务。也就是说,它们不会直接进入主线程执行,而是进入到微任务队列或下一次事件循环中的任务队列进行等待。

    常见的异步任务有:

    • XMLHttpRequest()

    • Promise.then()Promise.catch()Promise.finally()

    • setTimeout()setInterval()

    等待,就意味着不确定性。比如,XMLHttpRequest() 等待服务器响应,Promise.then() 等待 resolve()setTimeout() 等待时间。

    所以虽然都是异步任务,它们的执行的顺序仍然会有所区别。因此,我们将它们分为宏任务微任务

  6. 什么是宏任务?

    宏任务,即 MacroTask。就是指进入任务队列的任务。比如:

    由于当前任务队列已经处于执行状态,所以任务队列中遇到的宏任务将进入到下一次事件循环的任务队列,而微任务则会被放入到本次事件循环的微任务队列中。

  7. 什么是微任务?

    微任务,即 Microtask 或 Jobs。每次事件循环都会有一个初始为空的微任务队列。常见的微任务有:

    • Promise.then()Promise.catch()Promise.finally()

    • MutationObserver()(浏览器环境)

    • process.nextTick()(Node.js 环境)

  8. 测试题

    看到这里,JavaScript 的事件循环机制差不多就解释完了,涉及到了同步任务、异步任务、宏任务和微任务以及它们之间的关系。

    下面有几道测试题,同学们可以测试一下自己的理解程度:

    • 简单

      setTimeout(() => {
        console.log(1)
      }, 0)
      
      for (let i = 2; i <= 3; i++) {
        console.log(i)
      }
      
      console.log(4)
      
      setTimeout(() => {
        console.log(5)
      }, 0)
      
      for (let i = 6; i <= 7; i++) {
        console.log(i)
      }
      
      console.log(8)
      
    • 普通

      console.log(1)
      
      async function async1() {
        await async2()
        console.log(2)
      }
      
      async function async2() {
        console.log(3)
      }
      
      async1()
      
      setTimeout(() => {
        console.log(4)
      }, 0)
      
      new Promise(resolve => {
        console.log(5)
        resolve()
      })
        .then(() => {
          console.log(6)
        })
        .then(() => {
          console.log(7)
        })
      
      console.log(8)
      
    • 困难

      console.log(1)
      
      function a() {
        return new Promise(resolve => {
          console.log(2)
          setTimeout(() => {
            resolve()
            console.log(3)
          }, 0)
        })
      }
      
      a().then(() => {
        console.log(4)
      })
      

参考资料:

上一篇 下一篇

猜你喜欢

热点阅读