[深刻04] 事件循環

導航

[深刻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
[react] Hookshtml

[部署01] Nginx
[部署02] Docker 部署vue項目
[部署03] gitlab-CI前端

[源碼-webpack01-前置知識] AST抽象語法樹
[源碼-webpack02-前置知識] Tapable
[源碼-webpack03] 手寫webpack - compiler簡單編譯流程vue

js單線程

  • 爲何js回事單線程?
  • 設計js的目的是爲了操做DOM等,若是是多線程,兩個線程同時對同一個DOM元素執行了不一樣的操做,就會形成( 爭奪執行權 )的問題

進程和線程的區別

  • 一個進程能夠有多個線程
  • 一個線程只能屬於一個進程
  • 進程有本身獨立的地址空間,一個進程崩掉不會影響其餘進程
  • 線程只是一個進程的不一樣執行路徑,線程有本身的堆棧和局部變量,但線程之間沒有單獨的地址空間,一個線程死掉就等於整個線程死掉

一些單詞

  • stack棧
  • heap堆
  • queue隊列
  • macro-task: 宏任務
  • micro-task: 微任務
  • execution-context:執行上下文

棧和隊列

  • 棧:後進先出
  • 隊列:先進先出

同步任務,異步任務

  • 同步任務:在主線程上排隊執行的任務,只有前一個任務執行完畢,纔會執行後面的任務
  • 異步任務:未進入主線程,而進入任務隊列的任務,只有"任務隊列"通知主線程,某個異步任務能夠執行了,而且主線程的同步任務執行完畢後,該任務纔會進入主線程執行

事件循環

  • 一個線程中,事件循環是惟一的,但 任務隊列 能夠擁有多個
  • 任務隊列 包括: macro-task宏任務 ,和 micro-task微任務,在新標準中分別叫作 taskjobs
  • 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等教程任務源
  • 執行任務:進入任務隊列的是任務源分發的執行任務,即回調函數
  • 進入任務隊列的是:任務源分發的執行任務,即回調函數

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代碼的執行順序
  • 每個任務的執行,不管是macro-task仍是micro-task,都是藉助函數調用棧來完成
  • 事件循環的順序:
  1. 事件循環從宏任務開始,即從script總體代碼開始第一次循環
  2. 全局執行上下文進入函數調用棧(執行上下文棧),而後同步任務按調用順序依次進入,同步任務進入主線程,異步任務進入分線程,定時器/事件等被瀏覽器的對應模塊執行(定時器模塊,事件處理模塊),直到函數調用棧被清空(只剩全局,全局執行上下文永遠在棧底)即同步任務執行完
  3. 而後執行任務隊列中的微任務micro-task
  4. 當全部的micro-task微任務都執行完成後,循環再次從macro-task宏任務開始,而後再執行全部的micro-task,這樣一直循環下去。
  5. 其中每個任務的執行,不管是macro-task仍是micro-task,都是藉助函數調用棧(執行上下文棧)來完成的,即都是主線程的同步任務執行完後,任務隊列的任務才進入主線程執行,即進入函數調用棧

解析:
(1) stack是執行上下文棧(函數調用棧),最底部是全局執行上下文,上面是初始化函數
// 注意:函數分爲初始化函數和回調函數,這樣區分只是爲了更好的理解事件循環而已
(2) 好比定時器,js會把定時器的回調和時間,交給瀏覽器的(定時器管理模塊),在分線程上去執行,定時器管理模塊僅僅是計時,時間到後,把回調函數放入到任務隊列callback queue中,等待進入主線程執行。
(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 - 鞏固學習html5

<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] 繼承node

yangbo5207.github.io/wutongluo/j…
juejin.im/post/5aacd1…
個人語雀:www.yuque.com/woowwu/msyq…react

相關文章
相關標籤/搜索