JS進階 - 瀏覽器工做原理

1、瀏覽器的結構

瀏覽器的主要組件爲:css

  • 用戶界面 - 包括地址欄、前進/後退按鈕、書籤菜單等。除了瀏覽器主窗口(顯示頁面),其餘部分都屬於用戶界面。
  • 瀏覽器引擎 - 在用戶界面和渲染引擎之間傳送指令。
  • 渲染引擎 - 顯示(渲染)請求的內容。若是請求的內容是 HTML,它就負責解析 HTML 和 CSS 內容,並將解析後的內容顯示在屏幕上。
  • 網絡 - 用於網絡調用,好比 HTTP 請求。其接口與平臺無關,併爲全部平臺提供底層實現。
  • 用戶界面後端 - 用於繪製基本的窗口小部件,好比組合框和窗口。公開了與平臺無關的通用接口,在底層使用操做系統的用戶界面方法。
  • JavaScript 解釋器。用於解析和執行 JavaScript 代碼。
  • 數據存儲。這是持久層。瀏覽器須要在硬盤上保存各類數據,例如 Cookie。新的 HTML 規範 (HTML5) 定義了「網絡數據庫」,這是一個完整(可是輕便)的瀏覽器內數據庫。

2、渲染引擎

渲染引擎負責渲染——即渲染HTML/XML文檔或者圖片(經過插件能夠渲染PDF等等)。渲染引擎有html

  • Chrome/Safari - Webkit
  • Firefox - Gecko
  • Edge - EdgeHTML(不在本文討論範圍)

(一)渲染主流程

瀏覽器從網絡層獲取請求的文檔內容,而後開始渲染流程:前端

  • 解析並開始構建 content tree(element --> DOM nodes),同時解析樣式數據(外部CSS和style元素);
  • 二者結合構建 render tree(渲染樹包含帶有視覺屬性(如顏色和尺寸)的矩形們)
  • 在渲染樹建立後進入 Layout 階段,給渲染樹的每一個節點設置在屏幕上的位置信息
  • Paint 階段,經過 UI backend 繪製 render tree 到屏幕。

注意,渲染過程是漸進式的。瀏覽器會盡早展現文檔內容,即不會在全部HTML文檔解析完成後纔會去構建render tree,而是部份內容被解析和展現,並繼續解析和展現剩下的。html5

對chrome而言,渲染的具體流程是node

對firefox而言,git

(二)處理腳本和樣式表的順序

  1. script 是同步的github

    web模型一直是同步的,即網頁做者但願引擎遇到<script>標籤時能夠當即解析並執行——中止解析HTML,執行腳本(若是是外部腳本,先下載)。能夠用defer屬性指定腳本是異步的——不會中止文檔解析,在文檔解析完成後執行。web

  2. Speculative parsing(預解析)chrome

    當執行腳本時,其它線程會解析剩下的文檔,找出裏面的外部資源(script/style/img)來提早加載(能夠並行加載)。這種解析只是去查找須要加載的外部資源,不會修改content tree。數據庫

    因此咱們能夠看到多個外部資源並行下載。

  3. 樣式

    樣式表有不一樣的模型。理論上,樣式表不會更改 DOM tree,彷佛沒有必要等待樣式表並中止文檔解析。但有個問題,若是在文檔解析階段,腳本訪問樣式信息怎麼辦?Firefox會在腳本加載和解析階段禁止全部的腳本;對於 WebKit 而言,僅當腳本嘗試訪問的樣式屬性可能受還沒有加載的樣式表影響時,它纔會禁止該腳本。

這就是爲何推薦樣式放在<head>裏而腳本放在<body>底部。

(三)Render tree construction

構建 DOM tree的同時,瀏覽器還會構建另外一個樹:渲染樹(render tree)。這是由可視化元素按照其顯示順序而組成的樹,也是文檔的可視化表示。它的做用是保證按照正確的順序來繪製內容。

渲染樹的每一個節點(renderer)表明一個矩形區域——對應DOM元素的CSS Box。

renderer 和 DOM元素對應,但非一一對應。好比display:none的元素沒有對應的renderer;好比select對應3個renderer(display area/drop down list box /button)。另外,根據css spec,一個inline元素只能包含一個block元素或者多個inline元素,若是不符規則,就會建立anonymous block renderer。

有些 renderers 與對應的 DOM 節點,在各自樹中的位置不一樣。好比浮動定位和絕對定位的元素,它們在normal flow以外,放置在樹的其它地方,並映射到真正的renderer,而放在原位的是placeholder renderer。

漸進式處理

WebKit 使用一個標記來表示是否全部的頂級樣式表(包括 @imports)均已加載完畢。若是在attaching(DOM+CSSOM --> Render tree)過程當中樣式還沒有徹底加載,則使用佔位符,並在文檔中進行標註,等樣式表加載完畢後再從新計算。

(四)Layout

renderer在建立完成並添加到render tree時,並不包含 位置和大小 信息。計算這些值的過程稱爲佈局或重排(Layout/Reflow)。

HTML 採用基於流的佈局模型,這意味着大多數狀況下只要一次遍歷就能計算出幾何信息。處於流中靠後位置元素一般不會影響靠前位置元素的幾何特徵,所以佈局能夠按從左至右、從上至下的順序遍歷文檔。

Dirty 位系統

爲避免對全部細小更改都進行總體佈局,瀏覽器採用了一種「dirty 位」系統。若是renderer有更改,或者其自身及其children被標註爲「dirty」——則須要進行佈局。

有兩種標記:「dirty」和「children are dirty」。「children are dirty」表示renderer自身沒有變化,但它的children須要佈局。

全局佈局和增量佈局

全局佈局是指觸發了整個render tree的佈局,觸發緣由可能包括:

  • 影響全部renderers的全局樣式更改,例如字體大小更改。
  • 屏幕大小調整。

佈局能夠採用增量方式,也就是隻對 dirty 的 renderer 進行佈局(這樣可能存在須要進行額外佈局的弊端)。

當renderer爲 dirty 時,觸發增量佈局(異步)。例如,當來自網絡的額外內容添加到 DOM 樹以後,新的renderer附加到了render tree中。

異步佈局和同步佈局

  • 增量佈局是異步執行的。

    請求樣式信息(如「offsetHeight」)的腳本可觸發同步增量佈局。

  • 全局佈局每每是同步執行的。

  • 有時,當初始佈局完成以後,若是一些屬性(如滾動位置)發生變化,佈局就會做爲回調而觸發。

優化

  • 若是layout由 resize 或者 renderer 的位置變化觸發,那麼尺寸就無需再計算,直接從緩存獲取;
  • 有些狀況若是隻是子樹變化(好比text更新),那麼layout無需從root開始。

佈局處理

佈局過程一般以下:

  • 父renderer肯定本身的寬度。

  • 父renderer依次處理子renderer,而且:

    • 放置子renderer(設置 x,y 座標)。
    • 若是有必要,調用子renderer的佈局(若是子renderer是 dirty 的,或者這是全局佈局,或出於其餘某些緣由),這會計算子renderer的高度。
  • 父renderer根據子renderer的累加高度以及邊距和補白的高度來設置自身高度,此值也可供父renderer的父renderer使用。

  • 將其 dirty 位設置爲 false。

寬度計算

renderer寬度是根據容器塊(container block)的寬度、renderer樣式中的「width」屬性以及邊距和邊框計算得出的。

換行

若是renderer在佈局過程當中須要換行,會當即中止佈局,並告知其父renderer須要換行。父renderer會建立額外的renderer,並對其調用佈局。

(五)Painting

在繪製階段,會遍歷render tree,並調用renderer的「paint」方法,將renderer的內容顯示在屏幕上。繪製工做是使用用戶界面基礎組件(UI infrastructure component)完成的。

全局繪製和增量繪製

和佈局同樣,繪製也分爲全局(繪製整個render tree)和增量兩種。在增量繪製中,部分renderer發生了更改,可是不會影響整個樹。更改後的renderer將其在屏幕上對應的矩形區域設爲無效,這致使 OS 將其視爲一塊「dirty 區域」,並生成「paint」事件。OS 會很巧妙地將多個區域合併成一個。

繪製順序

CSS2 defines the order of the painting process. This is actually the order in which the elements are stacked in the stacking contexts. This order affects painting since the stacks are painted from back to front.

block renderer的堆棧順序是:

  1. 背景顏色
  2. 背景圖片
  3. 邊框
  4. children
  5. 輪廓(outline)

動態變化

在發生變化時,瀏覽器會盡量作出最小的響應。好比元素的顏色改變後,只會對該元素進行重繪。元素的位置改變後,只會對該元素及其子元素(可能還有同級元素)進行佈局和重繪。添加 DOM 節點後,會對該節點進行佈局和重繪。

一些重大變化(例如增大「html」元素的字體)會致使緩存無效,使得整個render tree都會進行從新佈局和繪製。

結合整個render tree構建和lauout,paint階段,能夠去思考怎麼減小relayout/repaint。

渲染引擎的線程(The rendering engine's threads)

渲染引擎是單線程的。幾乎全部操做(除了網絡操做)都是在單線程中進行的。在 Firefox 和 Safari 中,該線程就是瀏覽器的主線程。而在 Chrome 瀏覽器中,該線程是tab進程的主線程。

網絡操做可由多個線程並行執行。並行鏈接數是有限的(一般爲 2~6 個)。

Event loop

The browser main thread is an event loop. It's an infinite loop that keeps the process alive. It waits for events (like layout and paint events) and processes them.

這裏可配合 #21 閱讀,結合上面一小段,可展開討論下。

在瀏覽器的具體實現裏,瀏覽器內核(渲染進程)是多線程的。其中最重要的線程有:

  • GUI線程,即本章所講的渲染引擎線程,負責解析HTML/CSS,構建DOM tree和 render tree,佈局和繪製等。

    頁面第一次展現,或者須要重繪(repaint)或因爲某種操做引起迴流(reflow)時,該線程運行。

  • JS線程,即JS引擎線程,負責解析JavaScript腳本,運行代碼。JS引擎一直等待着任務隊列中任務的到來,而後執行。

    一個Tab頁(渲染進程)中不管何時都只有一個JS線程在運行——JS是單線程的。

  • 其它線程。

GUI線程和JS線程是互斥的(由於JavaScript可操縱DOM)。這就是爲何JS長時間運行會致使瀏覽器失去響應。

 


 

加微信:boan910227,備註:大前端;進前端進階羣;

相關文章
相關標籤/搜索