做爲前端開發,熟悉B/S架構中的瀏覽器渲染原理很是重要。css
瀏覽器大體上由幾個部分組成:不一樣瀏覽器會有區別html
界面控件 – 包括地址欄,前進後退,書籤菜單等窗口上除了網頁顯示區域之外的部分前端
瀏覽器引擎 – 查詢與操做渲染引擎的接口web
渲染引擎 – 負責顯示請求的內容。好比請求到HTML, 它會負責解析HTML、CSS並將結果顯示到窗口中chrome
網絡 – 用於網絡請求, 如HTTP請求。它包括平臺無關的接口和各平臺獨立的實現數據庫
UI後端 – 繪製基礎元件,如組合框與窗口。它提供平臺無關的接口,內部使用操做系統的相應實現後端
JS解釋器 - 用於解析執行JavaScript代碼瀏覽器
數據存儲持久層 - 瀏覽器須要把全部數據存到硬盤上,如cookies。新的HTML5規範規定了一個完整(雖然輕量級)的瀏覽器中的數據庫 web database
安全
注意:chrome瀏覽器與其餘瀏覽器不一樣,chrome使用多個渲染引擎實例,每一個Tab頁一個,即每一個Tab都是一個獨立進程。 服務器
相對於線程,進程之間是不共享資源和地址空間的,因此不會存在太多的安全問題,而因爲多個線程共享着相同的地址空間和資源,因此會存在線程之間有可能會惡意修改或者獲取非受權數據等複雜的安全問題
瀏覽器常駐線程:
GUI渲染線程負責渲染瀏覽器界面HTML元素,當界面須要重繪(Repaint)或因爲某種操做引起迴流(reflow)時,該線程就會執行。在Javascript引擎運行腳本期間,GUI渲染線程都是處於掛起狀態的,也就是說被凍結了.
JS爲處理頁面中用戶的交互,以及操做DOM樹、CSS樣式樹來給用戶呈現一份動態而豐富的交互體驗和服務器邏輯的交互處理。若是JS是多線程的方式來操做這些UI DOM,則可能出現UI操做的衝突;若是JS是多線程的話,在多線程的交互下,處於UI中的DOM節點就可能成爲一個臨界資源,假設存在兩個線程同時操做一個DOM,一個負責修改一個負責刪除,那麼這個時候就須要瀏覽器來裁決如何生效哪一個線程的執行結果,固然咱們能夠經過鎖來解決上面的問題。但爲了不由於引入了鎖而帶來更大的複雜性,JS在最初就選擇了單線程執行。
GUI渲染線程與JS引擎線程互斥的,是因爲JavaScript是可操縱DOM的,若是在修改這些元素屬性同時渲染界面(即JavaScript線程和UI線程同時運行),那麼渲染線程先後得到的元素數據就可能不一致。若是JS執行的時間過長,這樣就會形成頁面的渲染不連貫,致使頁面渲染加載阻塞的感受。
瀏覽器定時計數器並非由JS引擎計數的, 由於JS引擎是單線程的, 若是處於阻塞線程狀態就會影響記計時的準確, 所以經過單獨線程來計時並觸發定時是更爲合理的方案。
當一個事件被觸發時該線程會把事件添加到待處理隊列的隊尾,等待JS引擎的處理。這些事件能夠是當前執行的代碼塊如定時任務、也可來自瀏覽器內核的其餘線程如鼠標點擊、AJAX異步請求等,但因爲JS的單線程關係全部這些事件都得排隊等待JS引擎處理。
在XMLHttpRequest在鏈接後是經過瀏覽器新開一個線程請求,將檢測到狀態變動時,若是設置有回調函數,異步線程就產生狀態變動事件放到JS引擎的處理隊列中等待處理。
渲染過程:
用戶請求的HTML文本(text/html)經過瀏覽器的網絡層到達渲染引擎後,渲染工做開始。每次一般渲染不會超過8K的數據塊。
渲染流程有四個主要步驟:
解析HTML生成DOM樹 - 渲染引擎首先解析HTML文檔,生成DOM樹
構建Render樹 - 接下來不論是內聯式,外聯式仍是嵌入式引入的CSS樣式會被解析生成CSSOM樹,根據DOM樹與CSSOM樹生成另一棵用於渲染的樹-渲染樹(Render tree),
佈局Render樹 - 而後對渲染樹的每一個節點進行佈局處理,肯定其在屏幕上的顯示位置
繪製Render樹 - 最後遍歷渲染樹並用UI後端層將每個節點繪製出來
以上步驟是一個漸進的過程,爲了提升用戶體驗,渲染引擎試圖儘量快的把結果顯示給最終用戶。它不會等到全部HTML都被解析完才建立並佈局渲染樹。它會在從網絡層獲取文檔內容的同時把已經接收到的局部內容先展現出來。
渲染細節:
DOM樹的構建過程是一個深度遍歷過程:當前節點的全部子節點都構建好後纔會去構建當前節點的下一個兄弟節點。DOM樹的根節點就是document對象。
DOM樹的生成過程當中可能會被CSS和JS的加載執行阻塞,具體能夠參見下一章。當HTML文檔解析過程完畢後,瀏覽器繼續進行標記爲deferred模式的腳本加載,而後就是整個解析過程的實際結束觸發DOMContentLoaded事件,並在async文檔文檔執行完以後觸發load事件。
生成DOM樹的同時會生成樣式結構體CSSOM(CSS Object Model)Tree,再根據CSSOM和DOM樹構造渲染樹Render Tree,渲染樹包含帶有顏色,尺寸等顯示屬性的矩形,這些矩形的順序與顯示順序基本一致。從MVC的角度來講,能夠將Render樹當作是V,DOM樹與CSSOM樹當作是M,C則是具體的調度者,比HTMLDocumentParser等。
能夠這麼說,沒有DOM樹就沒有Render樹,可是它們之間不是簡單的一對一的關係。Render樹是用於顯示,那不可見的元素固然不會在這棵樹中出現了,譬如 <head>
。除此以外,display等於none的也不會被顯示在這棵樹裏頭,可是visibility等於hidden的元素是會顯示在這棵樹裏頭的。
DOM對象類型很豐富,什麼head、title、div,而Render樹相對來講就比較單一了,畢竟它的職責就是爲了之後的顯示渲染用嘛。Render樹的每個節點咱們叫它渲染器renderer。
上面肯定了renderer的樣式規則後,而後就是重要的顯示元素佈局了。當renderer構造出來並添加到Render樹上以後,它並無位置跟大小信息,爲它肯定這些信息的過程,接下來是佈局(layout)。
瀏覽器進行頁面佈局基本過程是以瀏覽器可見區域爲畫布,左上角爲 (0,0)
基礎座標,從左到右,從上到下從DOM的根節點開始畫,首先肯定顯示元素的大小跟位置,此過程是經過瀏覽器計算出來的,用戶CSS中定義的量未必就是瀏覽器實際採用的量。若是顯示元素有子元素得先去肯定子元素的顯示信息。
佈局階段輸出的結果稱爲box盒模型(width,height,margin,padding,border,left,top,…),盒模型精確表示了每個元素的位置和大小,而且全部相對度量單位此時都轉化爲了絕對單位。
在繪製(painting)階段,渲染引擎會遍歷Render樹,並調用renderer的 paint() 方法,將renderer的內容顯示在屏幕上。繪製工做是使用UI後端組件完成的。
迴流(reflow):當瀏覽器發現某個部分發生了點變化影響了佈局,須要倒回去從新渲染。reflow 會從<html>
這個 root frame
開始遞歸往下,依次計算全部的結點幾何尺寸和位置。reflow 幾乎是沒法避免的。如今界面上流行的一些效果,好比樹狀目錄的摺疊、展開(實質上是元素的顯示與隱藏)等,都將引發瀏覽器的 reflow。鼠標滑過、點擊……只要這些行爲引發了頁面上某些元素的佔位面積、定位方式、邊距等屬性的變化,都會引發它內部、周圍甚至整個頁面的從新渲染。一般咱們都沒法預估瀏覽器到底會 reflow 哪一部分的代碼,它們都彼此相互影響着。
重繪(repaint):改變某個元素的背景色、文字顏色、邊框顏色等等不影響它周圍或內部佈局的屬性時,屏幕的一部分要重畫,可是元素的幾何尺寸沒有變。
每次Reflow,Repaint後瀏覽器還須要合併渲染層並輸出到屏幕上。全部的這些都會是動畫卡頓的緣由。Reflow 的成本比 Repaint 的成本高得多的多。一個結點的 Reflow 頗有可能致使子結點,甚至父點以及同級結點的 Reflow 。在一些高性能的電腦上也許還沒什麼,可是若是 Reflow 發生在手機上,那麼這個過程是延慢加載和耗電的。能夠在csstrigger上查找某個css屬性會觸發什麼事件。
結合渲染流程,能夠針對性的優化渲染性能:
優化JS的執行效率
下降樣式計算的範圍和複雜度
避免大規模、複雜的佈局
簡化繪製的複雜度、減小繪製區域
優先使用渲染層合併屬性、控制層數量
對用戶輸入事件的處理函數去抖動(移動設備)
參考連接: