关于前端:深入04-事件循环

28次阅读

共计 6332 个字符,预计需要花费 16 分钟才能阅读完成。

导航

[[深刻 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()并不会在设置的工夫后执行,是因为同步工作的执行超过了定时器指定的工夫,// 因为工作队列的工作只有在主线程上的所有同步工作都执行完后(即调用栈中只剩全局上下文时),才会执行

示例 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] 执行上下文](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/…

正文完
 0