一. 瀏覽器的進程模型?
1.何為進程?
程序運行需要有它自己專屬的內存空間,可以把這塊內存空間簡單理解為進程?
每個應用至少有一個進程,進程之間相互獨立,即使要通信,也需要雙方同意??
2.何為線程?
有了進程后,就可以運行程序的代碼了,運行代碼的"人",稱之為"線程",一個進程至少有一個線程,所以在進程開啟后會自動創建一個線程來運行代碼,該線程成為主線程. 如果程序需要同時執行多塊代碼,主線程就會啟動更多的線程來執行代碼,所以一個進程中可以包含多個線程
3. 瀏覽器有哪些進程和線程?
瀏覽器是一個多進程多線程的應用程序
瀏覽器內部工作及其復雜?
為了避免相互影響,為了減少連環崩潰的幾率,當啟動瀏覽器后,它會自動啟動多個線程?
可以在瀏覽器的任務管理器中查看當前的所有進程
? 其中,最主要的進程有:?
1. 瀏覽器進程:?
? ? ?主要負責界面顯示,用戶交互,子進程管理等,瀏覽器進程內部會啟動多個線程處理不同的任務
2. 網絡進程
? ? 負責加載網絡資源,網絡進程內部會啟動多個線程來處理不同的網絡任務?
3.渲染進程(本節課重點講解的過程)
? ? 渲染進程啟動后,會開啟一個渲染主線程,主線程負責執行HTML,CSS,JS代碼.默認情況下,瀏覽器? ? ? 會為每個標簽頁開啟一個新的渲染進程,以保證不同的標簽頁之間不相互影響
二.渲染主線程是如何工作的?
1.渲染主線程是瀏覽器中最繁忙的線程,需要它處理的任務包括但不限于:?
- 解析 HTML?
- 解析 CSS
- 計算樣式
- 布局
- 處理圖層
- 每秒把頁面畫 60 次
- 執行全局 JS 代碼
- 執行時間處理函數?
- 執行計時器的回調函數?
- ......
要處理這么多的任務,主線程遇到了一個前所 未有的難題: 如何調度任務?
比如:?
- 我正在執行一個JS函數,執行到一半的時候用戶點擊了按鈕,我該立即去執行點擊事件的處理函數嗎?
- 我正在執行一個JS函數,執行到一半的時候某個計時器到達了時間,我該立即去執行它的回調嗎?
- 瀏覽器進程通知我"用戶點擊了按鈕",與此同時,某個計時器也到達了時間,我應該處理哪一個呢??
渲染主線程相處了一個絕妙的主意來處理這個問題: 排隊?
1.在最開始的時候,渲染主線程會進入一個無線循環?
2. 每一次循環會檢查消息隊列中是否有任務存在,如果有,就取出第一個任務執行,執行完一個進入下一次循環; 如果沒有,則進入休眠狀態?
3.其他所有線程(包括其他進程的線程)可以隨時向消息隊列添加任務,新任務會加到消息隊列的末尾,在添加新任務時,如果主線程是休眠狀態,則會將其喚醒以繼續循環拿去任務?
這樣一來,就可以讓每個任務有條不紊的,持續的進行下去了?
整個過程,被稱之為事件循環(消息循環)
三. 若干解釋?
1.何為異步?
代碼在執行過程中,會遇到 一些無法立即處理的任務,比如:??
- 計時完成后需要執行的任務 --- setTimeout,setInterval
- 網絡通信完成后需要執行的任務 -- XHR,Fetch?
- 用戶操作后需要執行的任務 -- addEventListener?
如果讓渲染主線程等待這些任務的時機達到,就會導致瀏覽器[卡死 ]
渲染主線程承擔這極其重要的工作,無論如何都不能阻塞!
因此,瀏覽器選擇異步來解決這個問題
使用異步的方式,渲染主線程永不阻塞
總結:? 單線程是異步產生的原因,事件循環是異步的實現方式?
2.面試題: 如何理解JS的異步?
JS是一門單線程的語言,這是因為它運行在瀏覽器的渲染主線程中,而渲染主線程只有一個,渲染主線程承擔著諸多的工作,渲染頁面,執行JS都在其中運行
如果使用同步的方式,就極有可能導致主線程產生阻塞,從而導致消息隊列中的很多其他任務無法得到執行,這樣一來,一方面會導致繁忙的主線程白白的消耗時間,另一方面導致頁面無法及時更新,給用戶造成卡死現象.?
所以瀏覽器采用異步的方式來避免,具體做法是當某些任務發生時,比如計時器,網絡,事件監聽,主線程將任務交給其他線程去處理,自身立即結束任務的執行,轉而執行后續代碼,當其他線程完成時,將事先傳遞的回調函數包裝成任務,加入到消息隊列的末尾排隊,等待主線程調度執行
在這種異步模式下,瀏覽器永不阻塞,從而最大限度的保證了單線程的流暢運行?
?3.JS為何會阻礙渲染?
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Demo Page</title>
</head><body><h1 id="mainHeading">Mr.Yuan is awesome!</h1><button id="changeButton">change</button><script>// 獲取DOM元素var h1 = document.querySelector('h1');var btn = document.querySelector('button');// 死循環指定的時間function delay(duration) {// 獲取當前時間戳var start = Date.now();// 循環直到指定的時間過去while (Date.now() - start < duration) { }}// 按鈕點擊事件處理btn.onclick = function () {h1.textContent = '袁老師很帥!';delay(3000); // 延遲3秒(同步阻塞)};</script>
</body></html>
問題: 點擊按鈕后會發什么?
1. 即使DOM更新(肉眼不可見)
代碼執行: h1.trextContent='袁老師很帥!'
- js會立即修改內存中的DOM樹,將<h1>的內容更改?
- 但此時頁面尚未重新渲染,用戶還看不到變化(可通過console.log(h1.texctContent)驗證值已改變)
2. 主線程被死循環阻塞(3秒卡頓)
- 代碼執行: deleay(3000)
- 現象
1. 瀏覽器主線程被while循環完全占用3秒?
2. 期間頁面完全凍結: 所有交互(點擊/滾動),動畫,其他腳本執行均無響應
3. 3秒后渲染生效(突然變化)
時間點:delay(3000)執行完畢?
- 主線程恢復控線狀態
- 瀏覽器出發渲染管線
- 用戶最終看到<h1>內容變為"袁老師很帥"
4.任務有優先級嗎?
任務沒有優先級,在消息隊列中先進先出
但消息隊列是有優先級的?
根據W3C的最新解釋:?
- 每個任務都有一個任務類型,同一個類型的任務必須在一個隊列,不同類型的任務可以分屬于不同的隊列,在一次時間循環中,瀏覽器可以根據實際情況從不同隊列中取出任務執行
- 瀏覽器必須準備好一個微隊列,微隊列中的任務優先所有其他任務執行
隨著瀏覽器的復雜度急劇提升,W3C 不再使用宏隊列的說法?
?在目前chrome的實現中,至少包含了下面的隊列:??
- 延時隊列: 用于存放計時器到達后的回調任務,優先級[中]
- 交互隊列: 用于存放用戶操作后產生的事件處理任務,優先級 [高]
- 微隊列: 用戶存放需要最快執行的任務,優先級[最高]
添加任務到微隊列的主要方式是使用Promise,MutationObsever?
例如:?
//立即把一個函數添加到微隊列
Promise.resolve().then(函數)
瀏覽器還有很多其他的隊列,由于和我們開發關系不大,不作考慮?
事件問題
1.
setTimeout(function () {console.log(1); }, 0);console.log(2);
1. 一開始執行全局JS,然后其他線程開始計時,計時過后,開始排隊?
2.
這里是有錯誤,應該是在延時隊列,后面有糾正?
3. 等全局js執行完之后, 把延時隊列的fn 放到主線程
4. 輸出 2 1
2.
function delay(duration) {var start = Date.now();while (Date.now() - start < duration) {} }setTimeout(function () {console.log(1); }, 0);console.log(2);
先輸出2 ,等待1s 輸出1??
3.
1
setTimeout(function () {console.log(1); }, 0);Promise.resolve().then(function () {console.log(2); });console.log(3);
2.
3. 輸出 3 2 1首先執行同步代碼 ,然后檢查并執行所有微任務(Promise回調),輸出2
最后執行延時隊列中的回調(setTimeout),輸出1?
4.?
1.?function a() {console.log(1);Promise.resolve().then(function () {console.log(2);}); }setTimeout(function () {console.log(3);Promise.resolve().then(a); }, 0);Promise.resolve().then(function () {console.log(4); });console.log(5);
1. 把輸出3的函數,和輸出4的函數,放入相對應的隊列中?
2. 全局執行完畢,先輸出5 ,在執行fn4 輸出4,在執行fn3 輸出3?
3. 執行fn3,把函數a放到微隊列
4. fn3執行完之后,把a放到渲染主線程?
5., a執行完之后輸出1吧輸出2的函數放到微隊列,然后輸出2
6. 最后輸出54312
面試題: 闡述一下JS的事件循環
參考答案:??
事件循環又叫做消息循環,是瀏覽器渲染主線程的工作方式.?
在Chrome的源碼中,它開啟一個不會結束的for循環,每次循環從消息隊列中取出第一個任務執行,而其他線程只需要在適合的時候將任務加入到隊列末尾即可.
過去吧消息隊列簡單分為宏任務和微隊列,這種說法目前已無法滿足復雜的瀏覽器環境,取而代之的是一種更加靈活多變的處理方式
根據W3C官方的解釋,每個任務有不同的類型,同類型的任務必須在同一個隊列,不同的任務可以屬于不同的隊列,不同任務隊列有不同的優先級,在一次事件循環中,由瀏覽器自行決定取哪一個隊列的任務,但瀏覽器必須有一個微隊列,微隊列的任務一定具有最高的優先級,必須優先調度執行
面試題: JS中的計時器能做到精確計時嗎?為什么?
參考答案:?
不行,因為:?
1. 計算機硬件沒有原子鐘,無法做到精確計時?
2. 操作系統的計時函數本身就有少量偏差,由于JS的計時器最終調用的是操作系統的函數,也就攜帶了這些偏差?
3. 按照W3C的標準,瀏覽器實現的計時器時,如果嵌套層級超過5層,則會帶有4毫秒的最少時間,這樣在計時時間少于4毫秒時又帶來了偏差
4.? 受事件循環的影響,計時器的回調函數只能在主線程空閑時運行,因此又帶來的偏差?