原文,Mariko Kosakajavascript
[譯]從內部瞭解現代瀏覽器(2)html
[譯]從內部瞭解現代瀏覽器(3)html5
這是本系列文章的的第3部分。 在前2篇,咱們介紹了多進程架構和導航流程。在這篇文章中,咱們將看看渲染器進程內部發生了什麼。 渲染器進程涉及Web性能的許多方面。 因爲渲染器過程當中發生了不少事情,所以本文僅做爲通常概述。 若是您想深刻挖掘裏面的細節,the Performance section of Web Fundamentals中有更多資源。java
渲染器進程負責選項卡內的全部事情。 在渲染器進程中,主線程處理您發送給用戶的大部分代碼。 若是您使用web worker
或service worker
,JavaScript的一部分可能將由工做線程處理。合成器線程和光柵線程也在渲染器進程內運行,以高效,流暢地呈現頁面。web
渲染器進程的核心工做是將HTML,CSS和JavaScript轉換爲用戶能夠與之交互的網頁。canvas
圖1:具備主線程,工做線程,合成器線程和柵格線程的渲染器進程瀏覽器
當渲染器進程收到導航的提交消息並開始接收HTML數據時,主線程開始解析文本字符串(HTML)並將其轉換爲文檔對象模型(DOM); DOM是瀏覽器頁面的內部表示,是Web開發人員能夠經過JavaScript與之交互的數據結構和API。 HTML標準規範將HTML文檔解析爲DOM。 您可能已經注意到,即便是錯誤的HTML也不會拋出異常。 例如,缺乏結束</p>
標記。 像Hi! <b>I'm<i> Chrome </b>!</i>
(b標籤在i標籤以前關閉)這樣的錯誤標記會被視爲<b>I'm<i> Chrome </i> </b> <i>!</i>
。 這是由於HTML規範旨在優雅地處理這些錯誤。 若是您對如何完成這些工做感到好奇,能夠閱讀HTML規範中的「解析器中的錯誤處理和異常狀況介紹」部分。緩存
網站一般會使用圖像,CSS和JavaScript等外部資源。 這些文件須要從網絡或緩存中加載。 主線程能夠在解析構建DOM時逐個請求它們,但爲了加快速度,「預加載掃描器」會同時運行。 若是HTML文檔中存在或之類的元素 ,則預加載掃描程序會檢查由HTML解析器生成的標記,並向網絡線程發送請求。性能優化
圖2:主線程解析dom並生成dom樹
當HTML解析器找到<script>
標記時,它會中止解析HTML文檔,而且必須加載,解析和執行JavaScript代碼。 爲何? 由於JavaScript可使用像document.write()
那樣改變整個DOM結構的東西來改變文檔的結構(HTML規範中的模型解析概述有一個很好的圖示)。 這就是HTML解析器在從新解析HTML文檔以前必須等待JavaScript運行結束的緣由。 若是您對JavaScript執行過程當中發生的事情感到好奇,V8團隊有對此的討論和博客文章。
Web開發人員能夠經過多種方式提示瀏覽器如何更好地加載資源。 若是您的JavaScript不使用document.write()
,則能夠向<script>
標記添加async或defer屬性。 而後,瀏覽器將異步加載和運行JavaScript代碼,不會阻止DOM解析。 若是合適,您也可使用JavaScript模塊。 <link rel =「preload」>
是一種通知瀏覽器當前導航一定須要該資源的方法,若是您但願儘快下載。 您能夠在資源優先級 - 瀏覽器幫助您瞭解更多信息。
擁有DOM並不足以知道頁面的外觀,由於咱們能夠在CSS中設置頁面元素的樣式。 主線程解析CSS並肯定每一個DOM節點的計算樣式。 這是有關基於CSS選擇器將哪一種樣式應用於某個元素的信息。 您能夠在DevTools的computed
部分中看到此信息。
圖3:主線程解析CSS以添加計算樣式
即便您不提供任何CSS,每一個DOM節點都具備它的計算樣式。 <h1>
標籤字體大於<h2>
標籤,每一個元素也都定義了邊距。 這是由於瀏覽器具備默認的樣式表。 若是您想知道Chrome的默認CSS是什麼樣的,您能夠在此處查看源代碼。
如今,渲染器進程知道每一個節點的文檔和樣式的結構,但這不足以呈現頁面。 想象一下,你正試圖經過手機向朋友描述一幅畫。 「有一個大的紅色圓圈和一個小的藍色方塊」,這些並不足以讓你的朋友瞭解這幅畫的樣子。
圖4:一我的經過電話向別人描述一幅畫的樣子
佈局是查找元素幾何位置的過程。 主線程遍歷DOM並計算樣式並建立佈局樹,其中包含x y座標和邊界框大小等信息。 佈局樹能夠是與DOM樹相似的結構,但它僅包含與頁面上可見內容相關的信息。 若是display:none
,則該元素不會是佈局樹的一部分(可是,visibility:hidden
的元素在佈局樹中)。 相似地,若是應用具備相似p :: before {content:「Hi!」}之類的僞類,則它也將包含在佈局樹中,即便它不在DOM中。
圖5:主線程經過計算樣式遍歷DOM樹並生成佈局樹
肯定頁面的佈局是一項具備挑戰性的任務。 即便是最簡單的頁面佈局,好比從上到下的標準流,也必須考慮字體的大小以及在哪裏劃分它們,由於它們會影響段落的大小和形狀; 而且影響下一段的位置。
CSS可使元素浮動到一側,隱去溢出項,而且更改寫入的方向。 你能夠想象,這個佈局階段是一項艱鉅的任務。 在Chrome中,有一整個工程師團隊負責佈局。 若是你想看到他們工做的細節,不多有關於BlinkOn會議的演講被記錄下來,很是有趣。
擁有DOM,樣式和佈局仍然不足以呈現頁面。假設您正在嘗試重現一幅畫。您知道元素的大小,形狀和位置,但您仍須要判斷繪製它們的順序。
圖7:一個拿着畫筆站在畫布前面的人,想知道是應該先畫圓圈仍是先畫方塊
例如,能夠爲某些元素設置z-index
,在這種狀況下,按HTML中編寫的元素順序繪製將致使不正確的呈現。
圖8:頁面元素按HTML標記的順序出現,致使錯誤的渲染圖像,由於沒有考慮z-index
在繪製步驟中,主線程遍歷佈局樹以建立繪製記錄。 繪畫記錄是一個繪畫過程的註釋,像是「背景優先,而後是文本,而後是矩形」。 若是您使用JavaScript繪製了<canvas>
元素,那麼您可能對此過程感到熟悉。
圖9:主線程遍歷佈局樹並生成繪製記錄
渲染流中最重要的是,在每一個步驟中,都使用前面操做的結果來建立新數據 例如,若是佈局樹中的某些內容發生更改,則須要爲文檔的受影響部分從新生成「繪製」順序
圖10:DOM + Style,Layout和Paint樹的生成順序
若是要爲元素設置動畫,則瀏覽器必須在每一個幀之間運行這些操做。 咱們的大多數顯示器每秒刷新屏幕60次(60 fps); 當你在每一幀移動屏幕時,動畫對人眼來講是平滑的。 可是,若是動畫遺漏了中間的幀,則頁面就會出現「janky」。
圖11:時間軸上的動畫幀
即便您的渲染操做與屏幕刷新保持一致,這些計算也會在主線程上運行,這意味着當您的應用程序運行JavaScript時,它可能會被阻塞。
圖12:時間軸上的動畫幀,但JavaScript阻止了一幀
您能夠將JavaScript操做劃分爲小塊,並計劃使用requestAnimationFrame()
在每一幀運行。 有關此主題的更多信息,請參閱優化JavaScript執行。 您也能夠在Web Workers中運行JavaScript以免阻塞主線程。
圖13:在動畫幀的時間軸上運行的較小的JavaScript塊
既然瀏覽器知道了文檔的結構、每一個元素的樣式、頁面的幾何形狀和繪製順序,那麼它如何繪製頁面呢?將這些信息轉換成屏幕上的像素稱爲光柵化。 處理這個問題的一種簡單的方法多是在視口內部使用光柵部件。若是用戶滾動頁面,則移動已光柵化的框架,並經過更多光柵填充缺乏的部分。這是Chrome首次發佈時處理光柵化的方式。然而,現代瀏覽器運行一個更復雜的過程,稱爲合成。
圖14:簡單光柵過程的動畫
合成是一種將頁面的各個部分分層,分別柵格化,並在稱爲合成器線程的單獨線程中合成爲頁面的技術。 若是發生滾動,因爲圖層已經光柵化,所以它所要作的就是合成一個新幀。 經過移動圖層和合成新幀,能夠以相同的方式實現動畫。 您可使用「圖層」面板查看您的網站在DevTools中如何劃分爲多個圖層。
圖15:合成過程的動畫
爲了找出哪些元素須要在哪些層中,主線程遍歷佈局樹以建立層樹(此部分在DevTools性能面板中稱爲「更新層樹」)。 若是頁面的某些部分應該是單獨的圖層(如滑入式側面菜單)卻沒有獲得一個,那麼您可使用CSS中的will-change
屬性提示瀏覽器。
圖16:主線程遍歷佈局樹生成層樹
您可能想到給每一個元素都添加層,可是在過多的層之間進行組合可能會致使操做速度比在每一幀中對頁面的小部分進行光柵化要慢,所以度量應用程序的渲染性能相當重要。有關主題的更多信息,請參閱Stick to Compositor-Only Properties和Manage Layer Count.
一旦建立了層樹並肯定了繪製順序,主線程將該信息提交給合成器線程。而後合成線程將每一個層進行柵格化。一個層可能很大,就像整個頁面的長度同樣,因此合成線程將它們分割成塊,並將每一個分塊發送到光柵線程。光柵線程光柵化每一個分塊並將它們存儲在GPU內存中。
圖17:光柵線程建立位圖併發送到GPU
合成器線程能夠對不一樣的aster線程進行優先級排序,以便視口(或附近)內的事物能夠先被光柵化。 一個層還具備多個不一樣分辨率的分塊,能夠處理放大操做等內容。
一旦分塊被光柵化,合成器線程會收集平鋪信息,稱爲繪製矩形,以建立一個合成幀
- | - |
---|---|
繪製矩形 | 包含諸如分塊在內存中的位置以及在考慮頁面合成的狀況下繪製分塊的頁面中的位置等信息。 |
合成幀 | 表示頁面的幀的繪製矩形集合。 |
而後經過IPC將合成幀提交給瀏覽器進程。 此時,能夠從UI線程添加另外一個合成幀以用於瀏覽器UI更改,或者從其餘渲染器進程添加擴展。 這些合成幀被髮送到GPU以在屏幕上顯示。 若是發生滾動事件,合成器線程會建立另外一個合成幀以發送到GPU。
圖18:合成器線程建立合成幀。先發送到瀏覽器進程,而後發送到GPU
合成的好處是它能夠在不涉及主線程的狀況下完成。 合成器線程不須要等待樣式計算或JavaScript執行。 這就是爲何僅合成動畫被認爲是平滑性能的最佳選擇。 若是須要再次計算佈局或繪圖,則必須涉及主線程。
在這篇文章中,咱們研究了從解析到合成的渲染過程。 但願您如今可以閱讀更多關於網站性能優化的內容。 在本系列的下一篇也是最後一篇文章中,咱們將更詳細地研究合成器線程,看看當用戶輸入(如鼠標移動和單擊)進入時發生了什麼。 你喜歡這個帖子嗎?若是您對之後的帖子有任何問題或建議,我很樂意在下面的評論部分或Twitter上@kosamari收到您的來信。