撥云見日、柳暗花明,看完了這篇文章,整個人唯一的感覺就是茅塞頓開,通篇的描述都可以說是深入淺出、面面俱到,強力推薦這篇文章!
掘金_「硬核JS」一次搞懂JS運行機制
https://juejin.cn/post/6844904050543034376#heading-15
一、行進與執行緒
1. 行程:
- “CPU資源分配的最小單位”
- 一個行程 = 該程式 + 該程式使用的記憶體 + 該程式使用的系統資源
- 一個CPU在一個時間點只能運行一個行程,借助 “時間片輪轉調度演算法” 實作多個行程之間的切換,以達到同時運行多個進行的目的
2. 執行緒:
- “CPU調度的最小單位”
- 不同的執行緒表示一個行程中不同的執行路線
- 一個進行可以有多個執行緒
3. 行程與執行緒的區別:
- 行程之間相互獨立,但同一行程下各個執行緒之間共享程式的記憶體以及程式級資源
4. 多行程與多執行緒:
- 多執行緒意味著在一個程式中,同一時間可以同時執行多個任務
- 多行程意味著可以在同一時間運行多個程式
5. 單執行緒的JS:
- 盡管借助Web Worker可以為JS創建多個執行緒,但這些執行緒都受到主執行緒的控制,因此本質上依舊是單執行緒
二、瀏覽器
1. 瀏覽器是多行程的
一個Tab頁就是一個行程,首頁也很消耗CPU
2. 瀏覽器都有哪些行程?
-
Browser行程
- 瀏覽器的主行程(負責界面顯示、互動、管理,網路資源的管理等),該行程只有一個
-
第三方插件行程
- 每種型別的插件對應一個行程,使用該插件時才創建
-
GPU行程
- 該行程也只有一個,用于3D繪制等等
-
渲染行程(重)
- 也就是瀏覽器內核(Renderer行程,內部是多執行緒)
- 每個Tab頁面都有一個渲染行程,互不影響,負責頁面渲染,腳本執行,事件處理等
3. 為什么瀏覽器是多執行緒?
- 避免被一不小心掛掉的插件或Tab頁把整個瀏覽器給堵死了
三、渲染行程(Renderer)
渲染行程的主要執行緒:
- GUI渲染執行緒
- 決議HTML、CSS生成DOM Tree、CSSOM,再組裝成Render Tree
- 重繪、回流時,對頁面的繪制
- GUI渲染執行緒與JS引擎執行緒是互斥的
- 當JS引擎執行時,GUI執行緒會被掛起(相當于凍結了),GUI更新會被保存在一個佇列中,等到JS引擎空閑時立即被執行
- JS引擎執行緒
- 也就是JS內核(例如Chrome的V8引擎),負責決議JavaScript腳本,管理著一個 “執行堆疊”
- 事件觸發執行緒
- 控制 “事件回圈”,管理著一個 “事件佇列”(event queue)
- 當JS執行時,如果遇到 “異步操作”,“事件觸發執行緒” 將事件添加到對應的執行緒中(比如定時器操作,便把定時器事件添加到定時器執行緒),等異步事件有了結果,便把它們的回呼添加到 “事件佇列”,等待JS引擎執行緒空閑時處理
- 定時觸發器執行緒(setTimeout、setInterval)
- 執行到定時器時,先在 “定時器執行緒” 進行 “定時” 與 “計時”,計時完畢后,將回呼函式交給 “事件觸發執行緒”,再由 “事件觸發執行緒” 將回呼函式添加到 “任務佇列” 中,等待JS引擎執行緒空閑后執行
- W3C規定,setTimeout中小于4ms的事件間隔算為4ms
- 異步http請求執行緒
- 執行到一個http請求時,先把 “異步請求事件” 添加到 “異步請求執行緒” 中,請求成功并收到回應后,把回呼交給 “事件觸發執行緒”,由 “事件觸發執行緒” 將回呼函式添加到 “任務佇列”
我們會發現,瀏覽器上所有執行緒的作業都很單一且獨立,非常符合 “單一原則”,
“定時觸發執行緒” 只管理定時器時只關注定時,不關心結果,定時結束就把回呼扔給“事件觸發執行緒”;
“異步http請求執行緒” 只管理http請求,同樣不關心結果,請求結束就把回呼扔給 “事件觸發執行緒”;
“事件觸發執行緒” 只關心將異步回呼推入任務佇列,
四、事件回圈(Event Loop)
1. 從“同步”、“異步”的角度看事件回圈
script分為 “同步任務” 與 “異步任務”,“同步任務” 在 “執行堆疊” 中執行,“異步任務” 有了運行結果,就將其回呼放入 “任務佇列” 中,“執行堆疊” 中的 “同步任務” 全部執行完畢,就開始讀取 “任務佇列”,依次添加到 “執行堆疊” 中執行,
注意,await以前的代碼,相當于new Promise的同步代碼,await以后的代碼相當與Promise.then的異步,

2. 從“宏任務”、“微任務”角度看事件回圈
1)宏任務(macrotask)
宏任務的每次每次執行都是從頭到尾地執行,不會在中途停止下來去執行其他任務,
常見的宏任務:
- script代碼
- setTimeout
- setInterval
- setImmediate
- requestAnimationFrame
requestAnimationFrame 比起 setTimeout、setInterval的優勢主要有兩點:
- requestAnimationFrame 會把每一幀中的所有DOM操作集中起來,在一次重繪或回流中就完成,并且重繪或回流的時間間隔緊緊跟隨瀏覽器的重繪頻率,一般來說,這個頻率為每秒60幀,
- 在隱藏或不可見的元素中,requestAnimationFrame將不會進行重繪或回流,這當然就意味著更少的的cpu,gpu和記憶體使用量,
2)微任務(microtask)
“微任務” 一般是 “宏任務” 執行過后的任務,
常見微任務
- process.nextTick()
- Promise.then()
- catch
- finally
- Object.observe
- MutationObserver
3)Event Loop流程
當一個 “宏任務” 執行完,會在渲染前,將執行期間所產生的所有 “微任務” 都執行完,然后執行下一個 “宏任務”,如此回圈往復,
宏任務 -> 微任務 -> GUI渲染 -> 宏任務 -> ...

渲染時,在一個GUI執行緒中,會將所有UI改動優化合并,
相反,如果對于一個div,在兩此宏任務中對其樣式進行更改,就比如背景顏色,那我們就可能看到閃爍,
3. 結合“同步任務”、“異步任務”、“宏任務”、“微任務”分析事件回圈

- 首先,“微任務” 的事件回呼在 “微任務佇列” 中,每個 “宏任務” 都有一個 “微任務佇列”
- <script>是最大的 “宏任務”,也是一個 “同步任務”,在主執行緒(JS引擎執行緒)中執行
- 執行時,若遇到 “異步任務”,判斷 “異步任務” 是 “微任務” 還是 “宏任務”
- 若是 “宏任務”,判斷這個 “宏任務” 是否擁有自己的 “專屬執行執行緒”
- 有,就把該任務交給 “專屬執行緒”,專屬執行緒完成對應事件后,將事件對應的回呼轉交給 “事件觸發執行緒”,事件觸發執行緒再將回呼推入 “事件佇列”
- 沒有時,直接將任務交給 “事件表”(event table),“事件表” 完成任務的事件后,也會將事件的回呼交給 “事件觸發執行緒”,依舊由 “事件觸發執行緒” 將回呼推入 “事件佇列”
- “微任務” 則相對簡單一些,直接交給屬于微任務的 “事件表”,在 “事件表” 完成對應事件后,再由同 “事件觸發執行緒” 將回呼推入 “微任務佇列”
- “主執行緒” 執行完<script>后,開始執行 “全域執行背景關系” 管理的 “微任務佇列”
- “微任務佇列” 執行完,再開始執行下一個 “宏任務”,一直這樣回圈下去,便是事件回圈Event Loop了,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/305705.html
標籤:其他
