前端性能優化:細說瀏覽器渲染的重排與重繪

前端性能優化由於涉及到計算機網絡、數據算法、圖形圖像處理、瀏覽器渲染等多方面計算機知識,常做爲前端工程師樂此不疲的技術討論話題,也正因如此,它也是面試時容易被問及的面試題之一。css

緣起

本篇文章緣起一次偶然的面試問答所引伸出的思考整理,着筆於瀏覽器渲染的角度,探討前端性能優化的思路和實踐建議,固然,瀏覽器渲染是一個複雜的過程,本文筆者將圍繞重排和重繪兩個關鍵詞開始行文。html

目錄結構

文章大體行文思路以下:前端

  • URL從輸入到頁面展現的過程web

  • DOM和JavaScript的關係面試

  • 爲何操做DOM會很「慢」算法

  • 瀏覽器解析HTML的過程api

  • 重排數組

  • 重繪瀏覽器

  • 優化方案緩存

URL從輸入到頁面展現的過程

在探討瀏覽器解析html以前,先了解url從輸入到最後頁面渲染的過程是一個頗有必要的步驟,它能夠幫助咱們把握總體流程,讓咱們在瞭解HTML解析細節以前知道它處於整個請求週期中的哪一階段,這對咱們構建完善知識圖譜頗有幫助。

首先,咱們假設輸入的url的請求爲最簡單的Http請求,以GET請求爲例,大體分如下幾個步驟:

  1. 用戶在瀏覽器的地址欄輸入訪問的URL地址。瀏覽器會先根據這個URL查看瀏覽器緩存-系統緩存-路由器緩存,若緩存中有,直接跳到第6步操做,若沒有,則按照下面的步驟進行操做。

  2. 瀏覽器根據輸入的URL地址解析出主機名。

  3. 瀏覽器將主機名轉換成服務器ip地址。瀏覽器先查找本地DNS緩存列表,看緩存裏面是否存在這個ip,若是有則進入第4步,若是緩存中不存在這個ip地址,就再向瀏覽器默認的DNS服務器發送查詢請求,同時緩存當前這個ip到DNS緩存列表中。更詳細步驟參考DNS查找域名的過程

  4. 拿到ip地址後,瀏覽器再從URL中解析出端口號。

  5. 拿到ip和端口後,瀏覽器會創建一條與目標Web服務器的TCP鏈接,也就是傳說中的三次握手。傳送門:完整的tcp連接

  6. 瀏覽器向服務器發送一條HTTP請求報文。

  7. 服務器向瀏覽器返回一條HTTP響應報文。

  8. 關閉鏈接 瀏覽器解析文檔。

  9. 若是文檔中有資源則重複六、七、8動做,直至資源所有加載完畢。

以上步驟簡述了瀏覽器從輸入url到最後頁面呈現的大體過程,但這並不很具體,好比瀏覽器請求報文類型是什麼,會遇到哪些錯誤場景、瀏覽器又是如何解析響應報文等等都沒具體描述。

實際上在http請求方式不一樣、有無代理、有無負載均衡等不一樣場景下訪問服務器的細節流程也會有一些差異,但這並不影響咱們對整個訪問環節的理解,有興趣的同窗可網上自行詳細瞭解,在此不作詳述。

DOM和JavaScript的關係

文檔對象模型(DOM)是一個獨立於語言,用於操做XML和HTML文檔的API,在web端,咱們經常使用來操做HTML,但其實DOM也是能夠操做XML文檔的。

咱們如今知道,DOM是一個獨立於語言的API,換句話說,DOM是一個與語言無關的API,別的語言也能夠實現操做DOM的具體api,可是它在瀏覽器中是用JavaScript來實現的,也所以,DOM是如今JavaScript編碼中很重要的一部分,由於JavaScript不少時候都在操做底層文檔。

爲何操做DOM會很慢

雖然DOM是由JavaScript實現的,可是在瀏覽器中都是把DOM和JavaScript分開來實現的,好比IE中,JavaScript的實現名爲JScript,放在jscript.dll文件中,而DOM則放在另外一個叫作mshtml.dll的庫中。在Safari中,DOM和渲染是使用Webkit中的WebCore實現,而JavaScript是由獨立的JavaScriptCore引擎實現,一樣在Chrome中,一樣是使用WebCore來實現渲染,而JavaScript引擎則是他們本身研發的V8引擎。

因爲DOM和JavaScript是被分開獨立實現的,所以,每一次在經過js操做DOM的時候,就須要先去鏈接js和DOM,咱們能夠這樣理解:把DOM和JavaScript比做兩個島,他們之間經過一個收費的橋鏈接着,每一次訪問DOM的時候,就須要通過這座橋,而且給「過路費」,訪問的次數越多,路費就會越高,而且訪問到DOM後,操做具體的DOM還須要給「操做費」,因爲瀏覽器訪問DOM的操做不少,所以,「路費」和「操做費」天然會增長,這就是爲何操做DOM會很慢的緣由

瀏覽器渲染HTML的步驟

HTML渲染大體分爲以下幾步:

  1. HTML被HTML解析器解析成DOM Tree, css則被css解析器解析成CSSOM Tree。

  2. DOM Tree和CSSOM Tree解析完成後,被附加到一塊兒,造成渲染樹(Render Tree)。

  3. 節點信息計算(重排),這個過程被叫作Layout(Webkit)或者Reflow(Mozilla)。即根據渲染樹計算每一個節點的幾何信息。

  4. 渲染繪製(重繪),這個過程被叫作(Painting 或者 Repaint)。即根據計算好的信息繪製整個頁面。

以上4步簡述瀏覽器的一次渲染過程,理論上,每一次的dom更改或者css幾何屬性更改,都會引發一次瀏覽器的重排/重繪過程,而若是是css的非幾何屬性更改,則只會引發重繪過程。因此說重排必定會引發重繪,而重繪不必定會引發重排。

重排(Relayout/Reflow)

在弄明白什麼是重排以前,咱們要知道:瀏覽器渲染頁面默認採用的是流式佈局模型(Flow Based Layout),這一點很重要。

所謂重排,其實是根據渲染樹中每一個渲染對象的信息,計算出各自渲染對象的幾何信息(DOM對象的位置和尺寸大小),並將其安置在界面中的正確位置。

因爲瀏覽器渲染界面是基於流式佈局模型的,也就是某一個DOM節點信息更改了,就須要對DOM結構進行從新計算,從新佈局界面,再次引起迴流,只是這個結構更改程度會決定周邊DOM更改範圍,即全局範圍和局部範圍,全局範圍就是從根節點html開始對整個渲染樹進行從新佈局,例如當咱們改變了窗口尺寸或方向或者是修改了根元素的尺寸或者字體大小等;而局部佈局能夠是對渲染樹的某部分或某一個渲染對象進行從新佈局。

在此,總結會引發重排的操做有:

  1. 頁面首次渲染。

  2. 瀏覽器窗口大小發生改變。

  3. 元素尺寸或位置發生改變。

  4. 元素內容變化(文字數量或圖片大小等等)。

  5. 元素字體大小變化。

  6. 添加或者刪除可見的DOM元素。

  7. 激活CSS僞類(例如::hover)。

  8. 設置style屬性

  9. 查詢某些屬性或調用某些方法。

常見引發重排屬性和方法
width height margin padding
display border position overflow
clientWidth clientHeight clientTop clientLeft
offsetWidth offsetHeight offsetTop offsetLeft
scrollWidth scrollHeight scrollTop scrollLeft
scrollIntoView() scrollTo() getComputedStyle()
getBoundingClientRect() scrollIntoViewIfNeeded()

重排也叫回流,實際上,reflow的字面意思也是迴流,之因此有的叫作重排,也許是由於重排更好理解,更符合中國人的思惟。標準文檔之因此叫作迴流(Reflow),是由於瀏覽器渲染是基於「流式佈局」的模型,流實際就使咱們常說的文檔流,當dom或者css幾何屬性發生改變的時候,文檔流會受到波動聯動的去更改,流就比如一條河裏的水,迴流就比如向河裏扔了一塊石頭,激起漣漪,而後引發周邊水流受到波及,因此叫作迴流,這樣理解彷佛更標準更規範,不過叫什麼並不重要,重要的是咱們真正理解了這個過程便好。

重繪(Repainting)

相比重排,重繪就簡單多了,所謂重繪,就是當頁面中元素樣式的改變並不影響它在文檔流中的位置時,例如更改了字體顏色,瀏覽器會將新樣式賦予給元素並從新繪製的過程稱。

常見引發瀏覽器繪製過程的屬性包含:

color border-style visibility background
text-decoration background-image background-position background-repeat
outline-color outline outline-style border-radius
outline-width box-shadow background-size

性能優化

咱們知道操做DOM是一個高成本的操做,不只是由於自己js與DOM的連接訪問,還包括操做DOM後悔引發一連串的連鎖反應(重排),所以,從性能優化角度,咱們能夠從如下幾個方面着手:

  • 減小DOM操做

    • 最小化DOM訪問次數,儘可能緩存訪問DOM的樣式信息,避免過分觸發迴流。

    • 若是在一個局部方法中須要屢次訪問同一個dom,則先暫存它的引用。

  • 採用更優的API替代消費高的api,轉換優化消費高的集合

    • 用querySelectorAll()替代getElementByXX()。

    • 開啓動畫的GPU加速,把渲染計算交給GPU。

    • 少用HTML集合(類數組)來遍歷,由於集合遍歷比真數組遍歷耗費更高。

    • 用事件委託來減小事件處理器的數量。

  • 減小重排

    • 避免設置大量的style屬性,由於經過設置style屬性改變結點樣式的話,每一次設置都會觸發一次reflow,因此最好是使用class屬性

    • 實現元素的動畫,它的position屬性,最好是設爲absoulte或fixed,這樣不會影響其餘元素的佈局

    • 動畫實現的速度的選擇。好比實現一個動畫,以1個像素爲單位移動這樣最平滑,可是reflow就會過於頻繁,大量消耗CPU資源,若是以3個像素爲單位移動則會好不少。

    • 不要使用table佈局,由於table中某個元素旦觸發了reflow,那麼整個table的元素都會觸發reflow。那麼在不得已使用table的場合,能夠設置table-layout:auto;或者是table-layout:fixed這樣可讓table一行一行的渲染,這種作法也是爲了限制reflow的影響範圍

  • css及動畫處理

    • 少用css表達式

    • 減小經過JavaScript代碼修改元素樣式,儘可能使用修改class名方式操做樣式或動畫;

    • 動畫儘可能使用在絕對定位或固定定位的元素上;

    • 隱藏在屏幕外,或在頁面滾動時,儘可能中止動畫;

最後總結

本篇文章主要抓取url從輸入到最後渲染成界面這一流程中的瀏覽器解析渲染HTML這一步驟來探討前端優化的思路和緣由,核心思想基於重排和重繪的關係來展開討論,主題大體有以下幾點:

  • url從輸入到最後渲染的大體環節。

  • 重排必定會重繪,重繪不必定有重排。

  • Js操做DOM是一個高消費過程。

  • 會引發重排/重繪的屬性和方法列舉

  • 優化思路(減小dom操做、替換高性能api、暫存引用、減小重排、開啓硬件加速等)。

最後,因爲我的水平緣由,如有行文不全或疏漏錯誤之處,懇請各位讀者批評指正,一路有你,不勝感激!

感謝這個時代,讓咱們能夠站在巨人的肩膀上,窺探程序世界的宏偉壯觀,我願以一顆赤子心,踏遍程序世界的千山萬水!願每個行走在程序世界的同仁,都活成心中想要的樣子,加油

相關文章
相關標籤/搜索