美文网首页
[深入04] 事件循环

[深入04] 事件循环

作者: woow_wu7 | 来源:发表于2021-07-25 12:26 被阅读0次

    导航

    [深入01] 执行上下文
    [深入02] 原型链
    [深入03] 继承
    [深入04] 事件循环
    [深入05] 柯里化 偏函数 函数记忆
    [深入06] 隐式转换 和 运算符
    [深入07] 浏览器缓存机制(http缓存机制)
    [深入08] 前端安全
    [深入09] 深浅拷贝
    [深入10] Debounce Throttle
    [深入11] 前端路由
    [深入12] 前端模块化
    [深入13] 观察者模式 发布订阅模式 双向数据绑定
    [深入14] canvas
    [深入15] webSocket
    [深入16] webpack
    [深入17] http 和 https
    [深入18] CSS-interview
    [深入19] 手写Promise
    [深入20] 手写函数

    [react] Hooks

    [部署01] Nginx
    [部署02] Docker 部署vue项目
    [部署03] gitlab-CI

    [源码-webpack01-前置知识] AST抽象语法树
    [源码-webpack02-前置知识] Tapable
    [源码-webpack03] 手写webpack - compiler简单编译流程
    [源码] Redux React-Redux01
    [源码] axios
    [源码] vuex

    js单线程

    • 为什么js回事单线程?
    • 设计js的目的是为了操作DOM等,如果是多线程,两个线程同时对同一个DOM元素执行了不同的操作,就会造成( 争夺执行权 )的问题

    进程和线程的区别

    • 一个进程可以有多个线程
    • 一个线程只能属于一个进程
    • 进程有自己独立的地址空间,<font color=red>一个进程崩掉不会影响其他进程</font>
    • 线程只是一个进程的不同执行路径,线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,<font color=red>一个线程死掉就等于整个线程死掉</font>

    一些单词

    • stack栈
    • heap堆
    • queue队列
    • macro-task: 宏任务
    • micro-task: 微任务
    • execution-context:执行上下文

    栈和队列

    • 栈:后进先出
    • 队列:先进先出

    同步任务,异步任务

    • <table><tr><td bgcolor=yellow>同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才会执行后面的任务</td></tr></table>
    • <table><tr><td bgcolor=orange>异步任务:未进入主线程,而进入任务队列的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,并且主线程的同步任务执行完毕后,该任务才会进入主线程执行</td></tr></table>

    事件循环

    • 一个线程中,事件循环是唯一的,但 <font color=red>任务队列</font> 可以拥有多个
    • <font color=red>任务队列</font> 包括:
      <font color=red>macro-task宏任务</font> ,和
      <font color=red>micro-task微任务</font>,在新标准中分别叫做
      <font color=red>task</font> 和
      <font color=red>jobs</font>
    • macro-task包括:script(整体代码),setTimeout,setInterval,setImmediate,I/O,UI render
    • micro-task包括:Promise,process.nextTick,MutationObserver(html5新特性)

    任务队列

    • 任务队列分为宏任务(macro-task)和 微任务(micro-task)也叫 task 和 jobs
    • 在一个线程中,任务队列可以有多个

    宏任务

    • 包括 script(整体代码),setTimeout,setInterval,setImmediate,I/O,UI render

    微任务

    • 包括 Promise,process-nextTick,MutationObserver(html5新特性)

    任务源和执行任务

    • 任务源:setTimeout/setInterval等教程任务源
    • 执行任务:<font color=red>进入任务队列的是任务源分发的执行任务,即回调函数</font>
    • <font color=red>进入任务队列的是:任务源分发的执行任务,即回调函数</font>

    setTimeout

    • 立即执行:setTimeout()函数本身是任务分发器,会立即执行
    • 延时执行:延时执行的是setTimeout的第一个参数,即回调函数,并且会进入macro-task
    window.setTimeout(function() {
      console.log('timer')
    }, 100)
    // setTimeout(callback, delay)会立即执行
    // setTimeout的一个参数会在dalay毫秒后执行,前提是同步任务执行的时间要小于delay毫秒
    // setTimeout()执行时,进入函数调用栈,或者叫执行上下文栈,执行完毕后,出栈
    // 而callback回调会在delay毫秒后进入任务队列,等待进入主线程
    // 任务队列:分为macro-task,micro-task
    
    
    
    indow.setTimeout(function() {
      console.log('timer')
    }, 0)
    // setTimeout()的第二个参数是0,表示在执行完所有同步任务后,第一时间执行回调
    // 关于setTimeout,即便主线程为空,0毫秒实际上也是达不到的。根据HTML的标准,最低是4毫秒
    
    

    事件循环的顺序!!!

    • 事件循环的顺序,决定了js代码的执行顺序
    • <font color=red>每一个任务的执行,无论是macro-task还是micro-task,都是借助函数调用栈来完成</font>
    • 事件循环的顺序:
    1. 事件循环从宏任务开始,即从script整体代码开始第一次循环
    2. 全局执行上下文进入函数调用栈(执行上下文栈),然后同步任务按调用顺序依次进入,同步任务进入主线程,异步任务进入分线程,定时器/事件等被浏览器的对应模块执行(定时器模块,事件处理模块),直到函数调用栈被清空(只剩全局,全局执行上下文永远在栈底)即同步任务执行完
    3. 然后执行任务队列中的微任务micro-task
    4. 当所有的micro-task微任务都执行完成后,循环再次从macro-task宏任务开始,然后再执行所有的micro-task,这样一直循环下去。
    5. 其中每一个任务的执行,无论是macro-task还是micro-task,都是借助函数调用栈(执行上下文栈)来完成的,即都是主线程的同步任务执行完后,任务队列的任务才进入主线程执行,即进入函数调用栈
    image
    解析:
    (1) stack是执行上下文栈(函数调用栈),最底部是全局执行上下文,上面是初始化函数
    // 注意:函数分为初始化函数和回调函数,这样区分只是为了更好的理解事件循环而已
    <font color=red>(2) 比如定时器,js会把定时器的回调和时间,交给浏览器的(定时器管理模块),在分线程上去执行,定时器管理模块仅仅是计时,时间到后,把回调函数放入到任务队列callback queue中,等待进入主线程执行。</font>
    (3) 比如事件,在事件发生的时候,浏览器的事件处理函数,会把回调放入到任务队列中
    setTimeout(cb, delay)
    // setTimeout()进入函数调用栈执行完后,出栈
    // cb和dalay进入浏览器的分线程,被浏览器的定时器管理模块执行,在delay时间后,将回调函数放入任务队列中
    
    // 有时候setTimeout()并不会在设置的时间后执行,是因为同步任务的执行超过了定时器指定的时间,
    // 因为任务队列的任务只有在主线程上的所有同步任务都执行完后(即调用栈中只剩全局上下文时),才会执行
    

    示例1

    <script>
      console.log('1');
      function fn1() {
          console.log('2');
      }
      fn1();
      setTimeout(() => { // macro-task任务
          console.log('3');
      }, 0)
      setTimeout(() => {
          console.log('4')
      }, 1000)
      new Promise((resolve) => { // micro-task
          console.log('5'); // 同步任务,同步任务按调用顺序依次执行
          return resolve()
      }).then(res => console.log('6')) // micro-task先执行
    </script>
    
    执行结果:1 2 5 6 3 4
    

    示例2 - 难度提升

    <script>
    // 定时器A
    setTimeout(() => {
      console.log('1')
      // 定时器B
      setTimeout(() => console.log('2'), 0)
      // a promise
      Promise.resolve().then(() => console.log('3'))
    }, 0)
    
    // b promise
    new Promise(resolve => {
      console.log('4')
      // 定时器C
      setTimeout(() => {
        console.log('5')
        return resolve()
      }, 0)
    }).then(() => console.log('6'))
    
    // 第一次Event loop
    // 宏任务 A
    // 微任务 b
    // 过程:执行宏任务script整体代码,执行完同步任务,清空micro-task,===> 输出4,宏任务队列:C A
    
    // 第二次Event loop
    // 宏任务 C A
    // 微任务
    // 过程:执行A, 宏任务:B C; 微任务:a; 清空micro-task, ===> 输出 1 3,宏任务队列: B C
    
    // 第三次Event loop
    // 宏任务 B C
    // 微任务
    // 过程:执行C, 清空micro-task, ===> 输出 5 6, 宏任务队列: B
    
    // 第四次Event loop
    // 宏任务:B
    // 微任务
    // 过程: 执行B,清空micro-task, ===> 输出 2
    
    // 结果: 4 1 3 5 6 2
    </script>
    

    示例3 - 巩固学习

    <script>
    // A 定时器
    setTimeout(() => {
        console.log('1');
        // b promise
        Promise.resolve().then(() => console.log('2'))
    }, 0);
    // B promise
    Promise.resolve().then(() => {
        console.log('3');
        // a定时器
        setTimeout(() => console.log('4'), 0)
    })
    
    // 第一次Event loop
    // 执行栈 window
    // 宏任务 A
    // 微任务 B
    // 过程:执行宏任务script,清空micro-task, 输出 3,并把 a定时器加入 宏任务队列 a A
    
    // 第二次Event loop
    // 宏任务 a A
    // 微任务 b
    // 过程:执行宏任务A, 添加微任务 b, 清空微任务 b
    
    // 第三次Event loop
    // 宏任务 a 
    // 微任务 
    // 过程:执行宏任务a
    
    // 结果:3 1 2 4
    </script>
    

    nodejs事件循环机制

    node事件循环机制
    
    
    1. timers阶段
    - timers阶段也叫定时器阶段,主要是计时和执行到点的计时器
    
    2. pending callbacks阶段
    - 系统相关,如tcp错误
    
    3. idea prepare 
    - 准备工作
    
    4. poll阶段(轮询队列)// poll是轮询的意思
    (1) 如果轮询队列不为空:依次从轮询队列中取出回调函数并执行,直到轮询队列为空或者达到系统的最大限制
    (2) 如果轮询队列为空:
        - 如果之前设置过setImmediate函数:直接进入下一个阶段check阶段
        - 如果之前没有设置过setImmedidate函数:在当前poll阶段等待
            - 直到轮询队列添加回调函数,就会4(1)的情况执行
            - 如果定时器到点了,也会去下一阶段check阶段
    
    5. check阶段
    - 执行setImmediate函数
    
    6. close callbacks 阶段
    - 执行close事件回调函数
    
    
    注意:process.nextTick()能在任意阶段优先执行
    

    综合案例

    nodejs事件循环
    
    
    setImmediate(() => {
      console.log('setImmediate')
    }) // 在poll阶段,轮询队列为空时,如果之前设置过setImmediate则直接跳到下一阶段check阶段就执行Immediate函数
    
    setTimeout(() => console.log('setTimeout'), 0) // 第一个阶段timer阶段,计时器delay是0,则在第一个阶段就执行
    
    process.nextTick(() => console.log('process.nextTick')) // 任意阶段优先执行
    
    
    执行结果:
    'process.nextTick'
    'setTimeout'
    'setImmediate'
    

    导航

    [深入01] 执行上下文
    [深入02] 原型链
    [深入03] 继承

    https://yangbo5207.github.io/wutongluo/ji-chu-jin-jie-xi-lie/shi-er-3001-shi-jian-xun-huan-ji-zhi.html
    https://juejin.im/post/6844903577052250119
    我的语雀:https://www.yuque.com/woowwu/msyqpd/ypfh8g

    相关文章

      网友评论

          本文标题:[深入04] 事件循环

          本文链接:https://www.haomeiwen.com/subject/eeobmltx.html