瀏覽器是多進程的
Browser進程:
瀏覽器的主進程(負責協調、主控),只有一個。
負責瀏覽器界面顯示,與用戶交互。如前進,後退等
負責各個頁面的管理,創建和銷燬其他進程
將Renderer進程得到的內存中的Bitmap,繪製到用戶界面上
網絡資源的管理,下載等
第三方插件進程:
每種類型的插件對應一個進程,僅當使用該插件時才創建
GPU進程:
最多一個,用於3D繪製等
瀏覽器渲染進程(瀏覽器內核)(Renderer進程,內部是多線程的):
默認每個Tab頁面一個進程,互不影響。主要作用爲
頁面渲染,腳本執行,事件處理等
新建頁面都會開啓一個新的進程
上面的進程會輔助這個進程的執行,其中渲染進程對頁面的影響最重要
怎麼查看瀏覽器進程情況
打開Chrome Shift+Esc;我們可以看到進程情況,Chrome圖標就是Browser主進程
多線程優缺點
避免單個page crash影響整個瀏覽器
避免第三方插件crash影響整個瀏覽器
多進程充分利用多核優勢
方便使用沙盒模型隔離插件等進程,提高瀏覽器穩定性
簡單點理解:如果瀏覽器是單進程,那麼某個Tab頁崩潰了,就影響了整個瀏覽器,體驗有多差;同理如果是單進程,插件崩潰了也會影響整個瀏覽器;而且多進程還有其它的諸多優勢,當然內存等資源消耗也會更大
重點是瀏覽器內核(渲染進程)該進程有多個線程完成
GUI渲染線程
負責渲染瀏覽器界面,解析HTML,CSS,構建DOM樹和RenderObject樹,佈局和繪製等。
當界面需要重繪(Repaint)或由於某種操作引發迴流(reflow)時,該線程就會執行
注意,GUI渲染線程與JS引擎線程是互斥的,當JS引擎執行時GUI線程會被掛起(相當於被凍結了),GUI更新會被保存在一個隊列中等到JS引擎空閒時立即被執行。
JS引擎線程
也稱爲JS內核,負責處理Javascript腳本程序。(例如V8引擎)
JS引擎線程負責解析Javascript腳本,運行代碼。
JS引擎一直等待着任務隊列中任務的到來,然後加以處理,一個Tab頁(renderer進程)中無論什麼時候都只有一個JS線程在運行JS程序
同樣注意,GUI渲染線程與JS引擎線程是互斥的,所以如果JS執行的時間過長,這樣就會造成頁面的渲染不連貫,導致頁面渲染加載阻塞。
事件觸發線程
歸屬於瀏覽器而不是JS引擎,用來控制事件循環(可以理解,JS引擎自己都忙不過來,需要瀏覽器另開線程協助)
當JS引擎執行代碼塊如setTimeOut時(也可來自瀏覽器內核的其他線程,如鼠標點擊、AJAX異步請求等),會將對應任務添加到事件線程中
當對應的事件符合觸發條件被觸發時,該線程會把事件添加到待處理隊列的隊尾,等待JS引擎的處理
注意,由於JS的單線程關係,所以這些待處理隊列中的事件都得排隊等待JS引擎處理(當JS引擎空閒時纔會去執行)
定時觸發器線程
傳說中的setInterval與setTimeout所在線程
瀏覽器定時計數器並不是由JavaScript引擎計數的,(因爲JavaScript引擎是單線程的, 如果處於阻塞線程狀態就會影響記計時的準確)
因此通過單獨線程來計時並觸發定時(計時完畢後,添加到事件隊列中,等待JS引擎空閒後執行)
注意,W3C在HTML標準中規定,規定要求setTimeout中低於4ms的時間間隔算爲4ms。
異步http請求線程
在XMLHttpRequest在連接後是通過瀏覽器新開一個線程請求
將檢測到狀態變更時,如果設置有回調函數,異步線程就產生狀態變更事件,將這個回調再放入事件隊列中。再由JavaScript引擎執行。
進程間的通信過程
- Browser進程收到用戶請求,首先需要獲取頁面內容(譬如通過網絡下載資源),隨後將該任務通過RendererHost接口傳遞給Render進程
- Renderer進程的Renderer接口收到消息,簡單解釋後,交給渲染線程,然後開始渲染
- 渲染線程接收請求,加載網頁並渲染網頁,這其中可能需要Browser進程獲取資源和需要GPU進程來幫助渲染
- 當然可能會有JS線程操作DOM(這樣可能會造成迴流並重繪)
- 最後Render進程將結果傳遞給Browser進程
- Browser進程接到結果並將結果繪製出來
渲染進程(瀏覽器內核)線程的關係
GUI渲染線程與JS引擎線程互斥
由於JavaScript是可操縱DOM的,如果在修改這些元素屬性同時渲染界面(即JS線程和UI線程同時運行),那麼渲染線程前後獲得的元素數據就可能不一致了。
因此爲了防止渲染出現不可預期的結果,瀏覽器設置GUI渲染線程與JS引擎爲互斥的關係,當JS引擎執行時GUI線程會被掛起,
GUI更新則會被保存在一個隊列中等到JS引擎線程空閒時立即被執行。
JS阻塞頁面加載
從上述的互斥關係,可以推導出,JS如果執行時間過長就會阻塞頁面。
譬如,假設JS引擎正在進行巨量的計算,此時就算GUI有更新,也會被保存到隊列中,等待JS引擎空閒後執行。
然後,由於巨量計算,所以JS引擎很可能很久很久後才能空閒,自然會感覺到巨卡無比。
所以,要儘量避免JS執行時間過長,這樣就會造成頁面的渲染不連貫,導致頁面渲染加載阻塞的感覺。
WebWorker,JS的多線程
JavaScript引擎是單線程運行的,JavaScript中耗時的I/O操作都被處理爲異步操作,它們包括鍵盤、鼠標I/O輸入輸出事件、窗口大小的resize事件、定時器(setTimeout、setInterval)事件、Ajax請求網絡I/O回調等。當這些異步任務發生的時候,它們將會被放入瀏覽器的事件任務隊列中去,等到JavaScript運行時執行線程空閒時候纔會按照隊列先進先出的原則被一一執行,但終究還是單線程。
創建Worker時,JS引擎向瀏覽器申請開一個子線程(子線程是瀏覽器開的,完全受主線程控制,而且不能操作DOM)
JS引擎線程與worker線程間通過特定的方式通信(postMessage API,需要通過序列化對象來與線程交互特定的數據)
//主線程 main.js
var worker = new Worker("worker.js");
worker.onmessage = function(event){
// 主線程收到子線程的消息
};
// 主線程向子線程發送消息
worker.postMessage({
type: "start",
value: 12345
});
//web worker.js
onmessage = function(event){
// 收到
};
postMessage({
type: "debug",
message: "Starting processing..."
});
瀏覽器渲染流程
拿到內容
- 瀏覽器根據 DNS 服務器得到域名的 IP 地址
- 向這個 IP 的機器發送 HTTP 請求
- 服務器收到、處理並返回 HTTP 請求
- 瀏覽器得到返回內容
解析內容建立Rendering Tree
解析HTMl構建dom
- dom作用: HTMLDOM是HTML Document Object Model(文檔對象模型)的縮寫,HTML DOM則是專門適用於HTML/XHTML的文檔對象模型。熟悉軟件開發的人員可以將HTML DOM理解爲網頁的API。它將網頁中的各個元素都看作一個個對象,從而使網頁中的元素也可以被計算機語言獲取或者編輯。
- dom規定
- 整個文檔是一個文檔節點
- 每個HTML標籤是一個元素節點
- 包含在HTML元素中的文本是文本節點
- 每一個HTML屬性是一個屬性節點(屬性節點是另一個層面的理解,在瀏覽器後臺打印的時候,不存在屬性節點)
- 註釋屬於註釋節點
- 解析過程:瀏覽器會自動把HTML文檔解析爲一個“文檔對象模型”,即Document Object Model,簡稱DOM,這是一個樹形結構,樹根是Document對象,樹幹是網頁的根元素<html>,然後分出兩個枝丫,一個是<head>,一個是<body>,然後網頁上的其他標籤就是這棵樹上的樹葉和樹枝了,通過這個結構,就可以查找和控制網頁上的任何一個元素了。因此,可以這麼說,網頁上的任何元素都是Document對象的子對象。
解析CSS產生CSS規則樹,css是由單獨的下載線程異步下載的,本身不會阻塞Dom加載,它和DOM結構比較像然後結合DOM生成RenderTree
Javascript解析, 通過 DOM API 和 CSSOM API 來操作 DOM Tree 和 CSS Rule Tree。
佈局render樹(Layout/reflow),負責各元素尺寸、位置的計算
繪製render樹(paint),繪製頁面像素信息
瀏覽器會將各層的信息發送給GPU,GPU會將各層合成(composite),顯示在屏幕上
渲染過程中的問題
DOMContentLoaded與onload
- 當 DOMContentLoaded 事件觸發時,僅當DOM加載完成,不包括樣式表,圖片
- 當 onload 事件觸發時,頁面上所有的DOM,樣式表,腳本,圖片都已經加載完成了。 (渲染完畢了)
- DOMContentLoaded -> load
瀏覽器如果渲染過程中遇到JS文件怎麼處理?
- 上面說過GUI渲染線程與JS引擎線程是互斥的,所以渲染過程中,如果遇到<script>就停止渲染,執行 JS 代碼,也就是說,在構建DOM時,HTML解析器若 遇到了JavaScript,那麼它會暫停構建DOM,將控制權移交給JavaScript引擎,等JavaScript引擎運行完畢,瀏覽器再從中斷的地方恢復DOM構建。
- 首屏渲染的越快,就越不應該在首屏就加載 JS 文件,這也是都建議將 script 標籤放在 body 標籤底部的原因
JS文件不只是阻塞DOM的構建,它會導致CSSOM也阻塞DOM的構建。
- 因爲JavaScript不只是可以改DOM,它還可以更改樣式,也就是它可以更改CSSOM。因爲不完整的CSSOM是無法使用的,如果JavaScript想訪問CSSOM並更改它,那麼在執行JavaScript時,必須要能拿到完整的CSSOM。
- 所以就導致了一個現象,如果瀏覽器尚未完成CSSOM的下載和構建,而我們卻想在此時運行腳本,那麼瀏覽器將延遲腳本執行和DOM構建,直至其完成CSSOM的下載和構建。也就是說,在這種情況下,瀏覽器會先下載和構建CSSOM
<script src="script.js"></script>
- 沒有 defer 或 async,瀏覽器會立即加載並執行指定的腳本,也就是說不等待後續載入的文檔元素,讀到就加載並執行。
<script defer src="script.js"></script>(延遲執行)
- defer 屬性表示延遲執行引入的 JavaScript,即這段 JavaScript 加載時 HTML 並未停止解析,這兩個過程是並行的。整個 document 解析完畢且 defer-script 也加載完成之後(這兩件事情的順序無關),會執行所有由 defer-script 加載的 JavaScript 代碼,然後觸發 DOMContentLoaded 事件。
- defer 與相比普通 script,有兩點區別:載入 JavaScript 文件時不阻塞 HTML 的解析,執行階段被放到 HTML 標籤解析完成之後。 在加載多個JS腳本的時候,async是無順序的加載,而defer是有順序的加載。
<script async src="script.js"></script> (異步下載)
- async 屬性表示異步執行引入的 JavaScript,與 defer 的區別在於,如果已經加載好,就會開始執行。也就是加載不阻塞,執行會阻塞。
瀏覽器的迴流與重繪 (Reflow & Repaint)
瀏覽器使用流式佈局模型 (Flow Based Layout)。
有了RenderTree,我們就知道了所有節點的樣式,然後計算他們在頁面上的大小和位置,最後把節點繪製到頁面上。
迴流
當Render Tree中部分或全部元素的尺寸、結構、或某些屬性發生改變時,瀏覽器重新渲染部分或全部文檔的過程稱爲迴流。
- 頁面首次渲染
- 瀏覽器窗口大小發生改變
- 元素尺寸或位置發生改變
- 元素內容變化(文字數量或圖片大小等等)
- 元素字體大小變化
- 添加或者刪除可見的DOM元素
- 激活CSS僞類(例如::hover)
- 查詢某些屬性或調用某些方法
重繪
當頁面中元素樣式的改變並不影響它在文檔流中的位置時(例如:color、background-color、visibility等),瀏覽器會將新樣式賦予給元素並重新繪製它,這個過程稱爲重繪。