進程:一個進程就是一個程序的運行實例。啓動一個程序的時候,操做系統會爲該程序建立一塊內存,用來存放代碼、運行中的數據和一個執行任務的主線程。html
線程:線程不能單獨存在,須要由進程來啓動和管理。算法
進程和線程的特色:canvas
瀏覽器全部功能模塊都運行在同一個進程裏,這些模塊包括網絡、插件、js運行環境、渲染引擎和頁面。帶來的問題:瀏覽器
早期多進程架構中,頁面運行在單獨的渲染進程中,插件也是單獨的插件進程,進程之間經過IPC通訊。這就解決了單進程帶來的問題:進程間相互隔離,不會互相影響,也不會互相阻塞,並且插件進程和渲染進程使用了安全沙箱,程序在沙箱中運行,不能在硬盤上讀寫數據。緩存
目前最新的Chrome進程架構圖:安全
包括:1個瀏覽器(Browser)主進程、1個GPU進程、1個網絡(NetWork)進程、多個渲染進程、多個插件進程。因爲渲染進程和插件進程須要運行用戶代碼,所以安全起見,被隔離在沙箱中。各進程功能:性能優化
多進程模型的缺點:服務器
確切地說,有鏈接關係的同一站點(協議相同、根域名相同)會共用一個渲染進程。 如下方法可使兩個標籤頁進行鏈接:markdown
<a href="./b.html" target="_blabk"> B </a>
打開B標籤頁,此時B標籤頁中能夠經過window.opener訪問A的window。這種有鏈接關係的標籤頁叫作瀏覽上下文組,Chrome瀏覽器會將瀏覽上下文組中屬於同一站點的標籤頁分配到同一個渲染進程中。但也有例外的狀況:網絡
<a target="_blank" ref="noopener noreferrer">
,其中noopener會將新開標籤頁的opener值設爲null,noreferrer表示新開的標籤頁不要有引用關係。這就使得新開標籤頁和當前標籤頁不屬於同一個瀏覽上下文組了。從用戶發出url請求到頁面開始解析的過程叫作導航。
從輸入url到頁面展現的流程:
確認提交後,導航流程結束,開始進入渲染流程。
渲染進程會先建立一個空白頁面,叫作解析白屏。以後開始頁面解析和子資源加載。
因爲瀏覽器沒法理解HTML,所以須要將HTML解析爲瀏覽器能理解的結構——DOM樹。DOM是保存在內存中的樹結構,DOM是生成頁面的基礎數據結構,給JavaScript腳本提供一套查詢和改變文檔結構、樣式、內容的接口,也是一道安全防禦線,一些不安全的內容在DOM解析階段就被排除了。
渲染引擎內部有個HTML解析器的模塊,負責將HTML字節流轉換爲DOM結構。解析過程是漸進的,網絡進程加載了多少數據,HTML解析器就解析多少數據。渲染進程從以前和網絡進程創建的數據管道的一端動態接收字節流,並將其解析爲DOM。
經過分詞器將字節流轉換爲Token,分爲Tag Token和文本Token。
將Token解析爲DOM節點,而後將節點添加到DOM樹中。HTML解析器維護了一個Token棧結構,若是是StartTag Token則入棧,並建立一個DOM節點添加到DOM樹中(HTML解析器最初會默認建立一個根爲document的空DOM結構);若是是文本Token則生成文本節點添加到DOM樹中;若是是EndTag Token則檢查棧頂是不是相同標籤的StartTag Token,是則出棧。
解析過程當中遇到了script標籤時,HTML解析器暫停工做,JavaScript引擎介入,執行腳本,此時只能訪問位於script之上已經構建了的DOM。訪問後面的元素會返回null,執行操做會報錯。腳本執行完,HTML解析器恢復解析。
若是不是內嵌腳本,而是經過src加載的腳本(有src屬性的腳本會忽略標籤內的代碼),會等待下載完成後執行,期間HTML解析器一直是暫停的狀態。不過好在Chrome瀏覽器的預解析操做會在渲染引擎收到字節流以後開啓一個預解析線程分析HTML文件中包含的JavaScript、CSS等相關文件而後提早下載這些文件。
在有src屬性加載腳本的script標籤中添加async屬性代表這個腳本是異步的,在後臺加載,HTML解析器繼續工做,等腳本加載完成後當即打斷HTML解析器(若是還沒解析完的話)開始執行腳本。 添加defer屬性也會異步加載,可是會等到HTML解析完畢,在DOMContentLoaded事件以前執行。這兩個屬性對無src屬性的腳本不會生效。
對於CSS,因爲不直接參與DOM構建,原本是不會阻塞DOM樹的構建的。可是若是頁面有同步執行的腳本時,由於執行前是不知道腳本有沒有操做CSSOM的,所以渲染引擎爲了不腳本執行出錯的可能性,直接假定腳本會依賴CSSOM。因而下載CSS文件並解析成CSSOM,再執行腳本。期間HTML解析器一直是暫停的狀態,直到腳本執行完畢才繼續工做。
頁面無腳本時CSS不會阻塞DOM樹的構建:
頁面有同步腳本時CSS會阻塞DOM樹的構建:
頁面有異步腳本時,會繼續構建DOM樹,腳本執行前須要等待CSS。是否繼續渲染則須要看CSS是否就緒,若是CSS還在加載中則須要等待。
若是是defer腳本,CSS加載完就開始渲染,只是DOMContentLoaded和load事件會延遲觸發;
若是是async腳本,DOMContentLoaded正常觸發,CSS加載完就開始渲染,load事件會等待腳本執行完再觸發:
總之CSS阻塞DOM樹的構建與否取決於腳本執行的時機。
樣式計算是爲了計算出DOM節點中每一個元素的具體樣式,這個階段大致能夠分爲三個步驟:
此階段用來計算DOM樹中可見元素的幾何位置。有兩個步驟:
因爲頁面中可能包含不少複雜的效果如3D變換、頁面滾動、z-index的z軸排序等,爲了不每次改動都要引起整個頁面重排或重繪,會將頁面分紅多個圖層,並生成一顆對應的圖層樹(LayerTree),層樹中的每一個節點都對應一個圖層,這些圖層按照必定順序疊加在一塊兒(層合成composite)構成了最終的頁面圖像。
一般,並不是佈局樹的每一個節點都佔用一個圖層,沒有圖層的節點將會從屬於父節點的圖層,最終每一個節點都會直接或間接包含在一個圖層中。處於相同的z軸座標空間時,就會造成一個圖層(或渲染層RenderLayers)。
渲染引擎會爲一些特定的節點建立單獨的圖層:
若是一個元素覆蓋在一個擁有獨立圖層的元素上,而且這個元素的z-index值更大,那這個元素也會被提高到單獨的圖層中。
像上面那種隱式建立圖層的狀況,若是不加註意,極端場景下可能會建立大量獨立圖層,也就是層爆炸,會佔用GPU和大量的內存資源,嚴重損耗頁面性能。瀏覽器也有相應的對策,瀏覽器的層壓縮機制,會將隱式建立的多個圖層壓縮到一個圖層中。
z-index:3這個元素是一個獨立的圖層,其後z-index值更大的三個元素被壓縮進了一個圖層中:
可是不少特定狀況下,瀏覽器是沒法進行層壓縮的。例如若是頁面的某一部分使用transform和animation製做動畫效果,因爲可能產生動態交疊的狀況,隱式建立圖層在不須要交疊的狀況下也能發生,致使頁面中全部z-index高於它的節點都被提高到單獨的圖層中,使得頁面產生了大量的獨立圖層。消除的辦法就是增大z-index的值,或者合理優化頁面元素的結構。
獲得圖層樹以後,渲染引擎會對圖層樹中的每一個圖層進行繪製,把一個圖層的繪製拆分紅不少小的繪製指令,再把這些指令按照順序組成一個待繪製列表:
圖層繪製階段輸出的內容就是這些待繪製列表。能夠在開發者工具中查看一個圖層的繪製列表,在區域2拖動進度條能夠重現列表的繪製過程:
當圖層的繪製列表準備好以後,渲染進程中的主線程會把該繪製列表提交給合成線程。
一般頁面內容都比屏幕大得多,若是等待全部圖層都繪製完畢再進行合成的話,會產生一些沒必要要的開銷,也會讓合成圖片的時間變得更久。合成線程會將圖層劃分爲固定的圖塊(tile),大小一般是256x256或512x512。若是這個圖層很是大,會優先處理視口(屏幕上頁面的可見區域)附近的圖塊。
渲染進程維護了一個柵格化的線程池,全部圖塊的柵格化都是在這個線程池內執行。 柵格化就是按照繪製列表的指令生成圖片,每一個圖層都對應一張圖片,合成線程將這些圖片合成爲「一張」圖片。圖塊是刪格化執行的最小單位。
一般,柵格化過程都會使用GPU來加速生成,這個過程叫作快速柵格化或GPU柵格化,渲染進程把生成位圖的指令發送給GPU,生成的位圖保存在GPU內存中。
但即便只繪製優先級最高的圖塊,也要耗費很多時間,由於會涉及到一個很關鍵的因素——紋理上傳(存儲在共享內存中的位圖將做爲紋理上傳到GPU,最後由GPU將多個位圖進行合成),這是由於從計算機內存上傳到GPU內存的操做會比較慢。Chrome採起的策略是,在首次合成圖塊的時候使用一個低分辨率的圖片,分辨率減小一半,紋理就減小了四分之三。在首次顯示頁面內容的時候,將這個低分辨率的圖片顯示出來,而後合成器繼續繪製正常比例的網頁內容,繪製完成後再替換掉當前顯示的低分辨率內容。
因爲合成操做在合成線程上完成,不會影響主線程,這就是爲何常常主線程卡住了但CSS動畫依然能執行的緣由。而使用JavaScript實現動畫時,會牽涉到整個渲染流水線,繪製效率底下。涉及到一些可使用合成線程來處理CSS特效或動畫的狀況,可使用will-change來提早告訴渲染引擎,讓它爲該元素準備獨立的圖層,而且開啓GPU加速。
例如will-change:transform,opacity;
告訴渲染引擎將會對該元素作一些特效變換,渲染引擎會爲該元素單獨分配一個圖層,當這些變換髮生時,渲染引擎會經過合成線程直接去處理變換,因爲沒有涉及到主線程,大大提高了渲染效率。因此CSS動畫比JavaScript動畫高效。
但這樣它佔用的內存也會增長,由於從層樹開始,後面每一個階段都會多一個層結構,這些都須要額外的內存。
一旦全部圖塊都被柵格化,合成線程就會生成一個繪製圖塊的指令——「DrawQuard」,而後將該指令提交給瀏覽器進程。瀏覽器進程中的viz組件接收到指令後,將頁面內容繪製到內存中,最後再將內存中的頁面顯示在屏幕上。
每一個顯示器都有固定的刷新頻率,一般是60Hz,即每秒更新60張圖片,更新的圖片都來自於顯卡的前緩衝區。顯示器每秒固定讀取60次前緩衝區中的圖像來顯示。顯卡中的GPU合成新的圖像,保存到後緩衝區。而後系統將顯卡的先後緩衝區互換,來讓顯示器讀取最新的圖像。一般,顯卡的更新頻率和顯示器的刷新頻率是一致的,但有時在一些複雜場景中,顯卡處理一張圖片的速度變慢,就形成了視覺上的卡頓。
頁面上的動畫效果例如滾動,渲染引擎經過渲染流水線生成新的圖片發送到顯卡的後緩衝區,要想實現流暢的動畫效果,渲染引擎須要每秒更新60張圖片到顯卡的後緩衝區。每一副圖片稱爲一幀,每秒更新了多少幀稱爲幀率,若是滾動過程當中1秒更新了60幀,幀率就是60Hz(或60FPS)。若是一次動畫中,渲染引擎生成某些幀的時間太久,用戶就會感受到卡頓。
渲染流程結束後,渲染進程會發送一個消息給瀏覽器進程,瀏覽器進程中止標籤圖標上的加載動畫。
幾個和性能優化相關的概念:
若是修改了元素的幾何位置屬性,如改變寬高,會引起從新佈局及以後一系列流程,這個過程叫作重排。
調用如下DOM API,爲了保證結果的準確性,瀏覽器會觸發重排以獲取最新的信息:
不改變幾何位置信息,只改變元素的如背景顏色等信息,引起從新繪製及以後一系列流程,這個過程叫作重繪。
若是既不要佈局也不要繪製的屬性,渲染引擎會跳過佈局和繪製,只執行後續的合成操做,這個過程叫作合成。例如使用CSS的transform來實現動畫效果,能夠避開重排和重繪階段,在非主線程上執行合成動畫的操做,無需佔用主線程的資源,大大提升了效率。
所以在執行效率方面:合成 > 重繪 > 重排。在性能優化方面能夠依據這個原則來調整方案。