4

[深入04] 事件循环

 2 years ago
source link: https://segmentfault.com/a/1190000040532500
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

[深入04] 事件循环

发布于 27 分钟前

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

[[react] Hooks](https://juejin.im/post/684490...

[[部署01] Nginx](https://juejin.im/post/684490...
[[部署02] Docker 部署vue项目](https://juejin.im/post/684490...
[[部署03] gitlab-CI](https://juejin.im/post/684490...

[[源码-webpack01-前置知识] AST抽象语法树](https://juejin.im/post/684490...
[[源码-webpack02-前置知识] Tapable](https://juejin.im/post/684490...
[[源码-webpack03] 手写webpack - compiler简单编译流程](https://juejin.im/post/684490...
[[源码] Redux React-Redux01](https://juejin.im/post/684490...
[[源码] axios ](https://juejin.im/post/684490...
[[源码] vuex ](https://juejin.im/post/684490...

js单线程

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

进程和线程的区别

  • 一个进程可以有多个线程
  • 一个线程只能属于一个进程
  • 进程有自己独立的地址空间,<font color=red>一个进程崩掉不会影响其他进程</font>
  • 线程只是一个进程的不同执行路径,线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,<font color=red>一个线程死掉就等于整个线程死掉</font>
  • stack栈
  • heap堆
  • queue队列
  • macro-task: 宏任务
  • micro-task: 微任务
  • execution-context:执行上下文
  • 栈:后进先出
  • 队列:先进先出

同步任务,异步任务

  • 同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才会执行后面的任务
  • 异步任务:未进入主线程,而进入任务队列的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,并且主线程的同步任务执行完毕后,该任务才会进入主线程执行
  • 一个线程中,事件循环是唯一的,但 <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>
  • 事件循环的顺序:
  • 事件循环从宏任务开始,即从script整体代码开始第一次循环
  • 全局执行上下文进入函数调用栈(执行上下文栈),然后同步任务按调用顺序依次进入,同步任务进入主线程,异步任务进入分线程,定时器/事件等被浏览器的对应模块执行(定时器模块,事件处理模块),直到函数调用栈被清空(只剩全局,全局执行上下文永远在栈底)即同步任务执行完
  • 然后执行任务队列中的微任务micro-task
  • 当所有的micro-task微任务都执行完成后,循环再次从macro-task宏任务开始,然后再执行所有的micro-task,这样一直循环下去。
  • 其中每一个任务的执行,无论是macro-task还是micro-task,都是借助函数调用栈(执行上下文栈)来完成的,即都是主线程的同步任务执行完后,任务队列的任务才进入主线程执行,即进入函数调用栈

解析:
(1) stack是执行上下文栈(函数调用栈),最底部是全局执行上下文,上面是初始化函数
// 注意:函数分为初始化函数和回调函数,这样区分只是为了更好的理解事件循环而已
<font color=red>(2) 比如定时器,js会把定时器的回调和时间,交给浏览器的(定时器管理模块),在分线程上去执行,定时器管理模块仅仅是计时,时间到后,把回调函数放入到任务队列callback queue中,等待进入主线程执行。</font>
(3) 比如事件,在事件发生的时候,浏览器的事件处理函数,会把回调放入到任务队列中

setTimeout(cb, delay)
// setTimeout()进入函数调用栈执行完后,出栈
// cb和dalay进入浏览器的分线程,被浏览器的定时器管理模块执行,在delay时间后,将回调函数放入任务队列中

// 有时候setTimeout()并不会在设置的时间后执行,是因为同步任务的执行超过了定时器指定的时间,
// 因为任务队列的任务只有在主线程上的所有同步任务都执行完后(即调用栈中只剩全局上下文时),才会执行
<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] 执行上下文](https://juejin.im/post/684490...
[[深入02] 原型链](https://juejin.im/post/684490...
[[深入03] 继承](https://juejin.im/post/684490...

https://yangbo5207.github.io/...
https://juejin.im/post/684490...
我的语雀:https://www.yuque.com/woowwu/...


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK