• 重学前端——事件循环


    事件循环

    js是单线程的 为什么要有事件循环机制哩?假设js只是单线程的 碰到同步任务还好,按照顺序依次执行,如果异步任务那种耗时很长的任务,比如发请求,那么这个js任务就卡住了 只能等到请求返回成功才能执行,这样效率就太低了。事件循环机制是为了解决这样的问题,让同步任务能跳过异步任务 并发的执行,等到异步任务完成后回调放入任务队列等待主线程调用执行。

    • 同步任务:在执行栈中按照先进后出的顺序执行,一个执行完了 再轮到下一个执行

    • 异步任务:不阻碍主线程的任务执行

    • 栈 先进后出,可以想像成一头被堵住的管道,只能从一头放入拿出,那先放入的压在下面,只能等上面的拿出后才能拿出。 函数调用时形成调用栈 当调用一个函数时 被压入栈中,函数执行完从栈中移除,执行权交给下一个函数。

    • 堆 对象被分配在堆中,堆是一个大块内存存储区域

    • 队列 先进先出,可以想象成两头通畅的水管,一头流入那一头流出。 js 运行时包含一个待处理消息的消息队列,每个消息都关联一个回调

      Js 任务类型:

      • 宏任务:script代码 setTimeout setInterval setImediate I/O UIRendering
      • 微任务: Process.nextTick Promise Mutation.Observer

      js代码在执行时 都有一个执行栈,按照先进后出的顺序执行,同步任务执行完从执行栈中弹出,异步任务会把回调放在任务队列中,在异步完成后,判断执行栈是否为空,如果为空会把任务队列中的任务放入执行栈,开启一次事件循环。任务队列中的任务分为两种,微任务和宏任务,先宏后微,第一次整体js代码作为一个宏任务,遇到宏任务就放任务队列,遇到微任务放任务队列,当执行栈空了就从任务队列拿微任务进行处理,执行完所有的微任务。当所有微任务完成,就开启下一轮事件循环,执行宏任务,宏任务执行完执行微任务。

      下面例子,执行过程:

      console.log('console start');
      setTimeout(function() {
          console.log('eventLoop2-宏任务1:setTimeout1');
          new Promise(function(resolve) {
              console.log('promise2');
              resolve()
          }).then(function() {
              console.log('eventLoop2-微任务1:then2');
          }).then(function() {
              console.log('eventLoop2-微任务2:then22');
          })
      })
      setTimeout(function() {
          console.log('eventLoop3-宏任务1:setTimeout2');
      })
      
      new Promise(function(resolve) {
          console.log('promise');
          resolve()
      }).then(function() {
          console.log('eventLoop1-微任务1:then');
      }).then(function() {
          console.log('eventLoop1-微任务2:then1');
      })
      
      console.log('console end');
      
      
      // console start
      // promise
      // console end
      // eventLoop1-微任务1:then
      // eventLoop1-微任务2:then1
      // eventLoop2-宏任务1:setTimeout1
      // promise2
      // eventLoop2-微任务1:then2
      // eventLoop2-微任务2:then22
      // eventLoop3-宏任务1:setTimeout2
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      1. 同步代码放入执行栈 执行后弹出 输出 console start。整体代码作为宏任务放入宏任务队列

      2. 接着setTimeout放入执行栈 执行后弹出,setTime callback (eventLoop2-宏任务1) 放入宏任务任务队列

      3. 接着setTimeout放入执行栈 执行后弹出,setTime callback(eventLoop3-宏任务1)放入宏任务任务队列

      4. new Promise 放入执行栈执行后弹出,输出 promisepromise.then callback(eventLoop1-微任务1)放入微任务队列 promise.then.then callback(eventLoop1-微任务2)放入微任务队列

      5. 执行同步代码 输出console end 此时执行栈为空

      6. 开启第一次事件循环,从宏任务队列对头那任务,执行整体宏任务,询问微任务是否为空

      7. 微任务队列不为空执行微任务队列,promise.then callback(eventLoop1-微任务1)放入执行栈执行完退出,输出 eventLoop1-微任务1:then

      8. 继续询问微任务队列是否为空 不为空 promise.then.then callback(eventLoop1-微任务2)放入执行栈 执行完退出 输出 eventLoop1-微任务2:then

      9. 继续询问微任务队列是否为空 为空。开启下一轮事件循环

      10. 拿宏任务执行,把setTime callback (eventLoop2-宏任务1)放入执行栈 执行完退出,打印eventLoop2-宏任务1:setTimeout1,继续执行new Promise 输出primise2,把 promise2的回调eventLoop2-微任务1:then2eventLoop2-微任务2:then22放入微任务队列

      11. 宏任务执行完 继续询问微任务 ,执行微任务 打印 eventLoop2-微任务1:then2eventLoop2-微任务2:then22

      12. 微任务队列为空 开启第三轮事件循环 执行setTime callback(eventLoop3-宏任务1) 打印eventLoop3-宏任务1:setTimeout2

      setTimeout(()=> {},0) 不是立即执行 而是等到执行栈为空再执行

      setTimeout(()=> {},1000) 不是1秒后执行 1秒只是最快执行时间

    事件循环和浏览器渲染

    并不是每一次事件循环都伴随着一次浏览器渲染,由屏幕刷新频率 页面性能 等共同决定。浏览器会尽可能保证帧率的稳定,如果无法维持60fps会降低到30fps 。

    • requestAnimationFrame 在重新渲染前调用

    • setTimeout 浏览器会把几次任务合并

      setTimeout(() => {
              console.log("sto")
              requestAnimationFrame(() => console.log("rAF"))
      })
      setTimeout(() => {
        console.log("sto")
        requestAnimationFrame(() => console.log("rAF"))
      })
      
      queueMicrotask(() => console.log("mic"))
      queueMicrotask(() => console.log("mic"))
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
    • requestIdleCallback 空闲调度算法 在屏幕渲染后执行,把一些计算量较大但没那么紧急的任务放到空闲时间去执行 不要影响浏览器中优先级高的任务。

  • 相关阅读:
    【趣学算法】分治算法
    【学习笔记】NOIP暴零赛
    Lua02——应用场景及环境安装
    前端css 纯数字或者字母 溢出不换行
    嵌入式UI框架 LVGL 学习笔记 02 页面管理和主题定制
    Maven打包时报错Could not resolve dependencies for project
    代码随想录算法训练营第七天
    OSPF的原理与配置
    【Java】图形、图像与音频(实验十二)
    【mindspore】【faster_rcnn】pad补齐之后的数据,如何将补齐的0那部分去掉
  • 原文地址:https://blog.csdn.net/zw52yany/article/details/125990258