前言:中心思想還是讓請求的資源得到更快響應的方法,比如壓縮資源,減少數據量的大小,緩存數據以減少請求數量,http/2讓網絡傳輸變得更快這些,下面就讓我們來看看瀏覽器是如何解析這些數據,最終又是如何將他們渲染在屏幕上的?在數據量不變的情況下還有哪些可以優(yōu)化的點?
瀏覽器怎么渲染的,從輸入一個路徑地址開始
當瀏覽器獲得一個html文件時,會“自上而下”加載,并在加載過程中進行解析渲染。
步驟: 構建DOM 和 CSSOM -> 構建render樹 -> 布局render樹 -> 繪制render樹
1. 將HTML解析成一個DOM(文檔對象模型)
瀏覽器會解析html文件中的標簽,例如:<html>
,</body>
,<p>
,給這些標簽打上標記,遇到標記后瀏覽器使用令牌生成器開始生成令牌,例如:<p>hello</p>
這樣一個文本標簽會生成:StartTag:p
, hello
, EndTag
:p這樣3個令牌,接著生成對應的nodes(節(jié)點)后就形成了DOM,也就是文檔對象模型(Document Object Model,簡稱DOM));

當html解析中遇到< link >標簽時,會請求對應的CSS文件,當CSS文件就位時便開始解析它(如果遇到行內< style
時則直接解析),這一解析過程可以和構建DOM同時進行。 那其實css樹構建的過程和dom樹類似。也是經歷了打標記, 生成令牌和節(jié)點的過程,不過這里的識別標記規(guī)則和dom不一樣,因為css擁有繼承屬性的操作方式,父級部分屬性樣式會被子級樣式繼承,比如字體font-size,
font-weight, font-style,
行高line-height等,還有部分默認樣式,如下圖箭頭所指便是默認的樣式,紅色框內是繼承樣式。
3. 構造 Rendering Tree。
根據DOM樹,會遍歷每一個可見的節(jié)點,對于每一個可見的節(jié)點,在CSSOM上找到匹配的樣式并應用,生成Rendering Tree。
此時渲染樹并不等同于構建DOM 樹,因為如果遇到不可見(display:none)屬性,會跳過該元素和其子項。另外在html的head里不包含任何可見信息,所以這部分的內容會被很快的去除;
4. 布局
有了Render Tree,瀏覽器已經能知道網頁中有哪些節(jié)點、各個節(jié)點的CSS定義以及他們的從屬關系。下一步就是計算出每個節(jié)點在屏幕中的位置和大小,這個操作稱之為Layout。
5. 繪制
即遍歷render樹,并使用UI后端層繪制每個節(jié)點以像素顯示在屏幕上。
這里需要注意的是:
1、 并不是所有的數據都已經加載出來了,而是先顯示一部分,再顯示另一部分。
2、 雖然是從上往下加載,但并不是一定要等著上面的內容顯示之后才顯示下面的內容,而是誰解析的快,就渲染的越快。
3、 CSS 加載不會阻塞 DOM 的加載,但是會阻塞 Dom 的渲染。雖然DOM 和 CSSOM 通常是并行構建的,但是Render Tree 是依賴于 DOM Tree 和 CSSOM Tree 的,所以他必須等待到 CSSOM Tree 構建完成,也就是 CSS 資源加載完成后,才能開始渲染。
如果引用的是外部樣式,需要等待這些樣式全部加載完后,才開始構建CSSOM。因為css文件中包含大量的樣式,后面的樣式會覆蓋前面的樣式,如果我們提前就構建CSSDOM,可能會得到錯誤的結果。
4、 JS文件不只是阻塞DOM的構建,它會導致CSSOM也阻塞DOM的構建;在構建DOM時,HTML解析器若遇到了JavaScript,那么它會暫停構建DOM,將控制權移交給JavaScript引擎,等JavaScript引擎運行完畢,瀏覽器再從中斷的地方恢復DOM構建。
這是因為JavaScript不只是可以改DOM,它還可以更改樣式,也就是它可以更改CSSOM。因為不完整的CSSOM是無法使用的,如果JavaScript想訪問CSSOM并更改它,那么在執(zhí)行JavaScript時,必須要能拿到完整的CSSOM。也就是說,在這種情況下,瀏覽器會先下載和構建CSSOM,然后再執(zhí)行JavaScript,最后在繼續(xù)構建DOM。
所以,如果我們想首屏渲染的越快,就越不應該在首屏就加載 JS 文件,這也是都建議將 script 標簽放在 body 標簽底部的原因。當然在當下,并不是說 script 標簽必須放在底部,因為你可以給 script 標簽添加 defer(延遲執(zhí)行) 或者 async(異步下載) 屬性;
關鍵渲染路徑優(yōu)化
關鍵渲染路徑是瀏覽器將 HTML、CSS、JavaScript
轉換為在屏幕上呈現(xiàn)的像素內容所經歷的一系列步驟。也就是我們剛剛提到的的的瀏覽器渲染流程。
1、將能快速解析的數據放在前面,不好解析的放在后面
html語義化標簽加強DOM解析,因為語義化標簽是瀏覽器內置就能解析識別的標簽;
盡量不要使用 table 布局。因為可能很小的一個小改動會造成整個 table 的重新布局,并且table不是一點一點加載渲染的,是一整個加載好了才渲染的;
一些裝飾性元素可以適當的使用偽元素,避免增加無意義的頁面元素;
2、讓CSSOM不阻塞DOM的渲染
并不是所有的CSS資源都那么的 『關鍵』。
舉個例子:一些響應式CSS只在屏幕寬度符合條件時才會生效,還有一些CSS只在打印頁面時才生效。這些CSS在不符合條件時,是不會生效的,所以我們?yōu)槭裁匆尀g覽器等待我們并不需要的CSS資源呢?
我們可以將打印相關的CSS移動到print.css,然后我們在HTML中引入CSS時,添加媒體查詢屬性print,代碼如下:
上面提供的方法是針對那些不需要生效的CSS資源,如果CSS資源需要在當前頁面生效,只是不需要在首屏渲染時生效,那么為了更快的首屏渲染速度,我們可以將這些CSS也設置成非關鍵資源。只是我們需要一些比較hack的方式來實現(xiàn)這個需求:
<link href="style.css" rel="stylesheet" media="print" onload="this.media='all'">
上面代碼先把媒體查詢屬性設置成print,將這個資源設置成非阻塞的資源。然后等這個資源加載完畢后再將媒體查詢屬性設置成all讓它立即對當前頁面生效。
類似的方案還有,代碼如下:
<link rel="preload" href="style.css" as="style" onload="this.rel='stylesheet'">
<link rel="alternate stylesheet" href="style.css" onload="this.rel='stylesheet'">
總結一下就是把首屏渲染需要使用的CSS通過style標簽內嵌到head標簽中,其余CSS資源使用異步的方式非阻塞加載。優(yōu)化過后可以對照看一下FP(首次繪制)的時間點有沒有提前。
3、盡可能的不使用 @import 屬性,import屬性會在頁面加載完成之后,等效于把css寫到文檔的底部,并且加載多個css文件時是串行加載,所以應該避免這種情況。
4、CSS 選擇符從右往左匹配查找,應避免節(jié)點層級過多,并適當使用子選擇器,例如 .list>a>img
5、異步JavaScript
為了避免阻塞,可以為script標簽添加async或者defer屬性。async的執(zhí)行是加載完成就會立馬去執(zhí)行,而不像defer那樣要等待所有的腳本加載完后按照順序執(zhí)行,下面可以聽過這個兩張圖來對比一下他們的區(qū)別:
藍色:文檔解析
紫色:腳本加載
黃色:腳本執(zhí)行
綠色: HTML 文檔被完全加載和解析完成
defer
適用于如果你的腳本代碼依賴于頁面中的DOM元素(文檔是否解析完畢),或者被其他腳本文件依賴時。
async
適用于如果你的腳本并不關心頁面中的DOM元素(文檔是否解析完畢),并且也不會產生其他腳本需要的數據時。
6、減少JS操作 DOM 的次數
把 DOM 和 JavaScript 各自想象成一個島嶼,它們之間用收費橋梁連接?!陡咝阅?JavaScript》
DOM 是屬于渲染引擎中的東西,而 JS 又是 JS 引擎中的東西。當我們用 JS 去操作 DOM 時,本質上是 JS 引擎和渲染引擎之間進行了“跨界交流”。這個“跨界交流”的實現(xiàn)并不簡單,它依賴了橋接接口作為“橋梁”。
過“橋”要收費——這個開銷本身就是不可忽略的。我們每操作一次 DOM(不管是為了修改還是僅僅為了訪問其值),都要過一次“橋”。過“橋”的次數一多,就會產生比較明顯的性能問題。因此“減少 DOM 操作”的建議,并非空穴來風。
1、最常見的緩存Dom對象
操作Dom一般首先會去訪問Dom,尤其是像循環(huán)遍歷這種事件復雜度可能會比較高的操作,那么可以在循環(huán)之前就將主節(jié)點,不必循環(huán)的Dom節(jié)點先獲取到,那么在循環(huán)里就可以直接引用,而不必去重新查詢。
2、vue虛擬節(jié)點
虛擬Dom是js模似DOM樹并對DOM樹操作的一種技術。virtual DOM是一個純js對象(字符串對象),所以對他操作會高效。
在dom發(fā)生變化的時候對虛擬dom進行操作,通過dom diff算法將虛擬dom和原虛擬dom的結構做對比,最終批量的去修該真實的dom結構,盡可能的避免了頻繁修改dom而導致的頻繁的重排和重繪。
減少 reflow/repaint
Reflow(重排):當瀏覽器發(fā)現(xiàn)某個部分發(fā)生了變化,影響了布局,我們需要重新驗證并計算Render Tree。是Render Tree的一部分或全部發(fā)生了變化——這就是Reflow,或是Layout。
Repaint(重繪):如果只是改變了某個元素的背景顏色,文字顏色等,不影響元素周圍或內部布局的屬性,將只會引起瀏覽器的重繪,重畫某一部分。
因此看出,重排要比重繪更花費時間,也就更影響性能。所以在寫代碼的時候,要盡量避免過多的重排。
以下操作會導致重排或重繪:
-
刪除,增加,或者修改DOM元素節(jié)點。
-
改變元素的大小,位置時,或者將使用display:none時,會造成重排;修改CSS顏色或者visibility:hidden等等,會造成重繪。
-
修改網頁的默認字體時。 Resize窗口的時候(移動端沒有這個問題),或是滾動的時候。 內容的改變,(用戶在輸入框中寫入內容也會)。
-
激活偽類,如:hover。
1、減少DOM操作,比如改變元素的尺寸,位置,顯隱等。可以預先定義好 css 的 class,然后修改 DOM 的 className;
2、為動畫的 HTML 元件使用 fixed 或 absoult 的 position,那么修改他們的 CSS 是不會 reflow 的。
3、隱藏元素,進行修改后,然后再顯示該元素。
4、可以使用文檔片段創(chuàng)建一個子樹,然后再拷貝到文檔中,文檔片段是一個輕量級的document對象,它設計的目的就是用于更新,移動節(jié)點之類的任務,而且文檔片段還有一個好處就是,當向一個節(jié)點添加文檔片段時,添加的是文檔片段的子節(jié)點群,自身不會被添加進去。
let fragment = document.createDocumentFragment();
appendNode(fragment, data);
ul.appendChild(fragment);
懶加載(延遲加載)
1、圖片懶加載
對頁面加載速度影響最大的就是圖片,一張普通的圖片可以達到幾M的大小,而代碼也許就只有幾十KB。當頁面圖片很多時,頁面的加載速度緩慢,幾S鐘內頁面沒有加載完成,用戶會失去耐心。
為了加速頁面加載速度,所以很多時候我們需要將頁面內未出現(xiàn)在可視區(qū)域內的圖片先不做加載, 等到滾動到可視區(qū)域后再去加載。參考庫:vue-lazyload
<img v-lazy="/static/img/1.png">
2、路由懶加載
把不同路由對應的組件分割成不同的代碼塊,然后當路由被訪問的時候才加載對應組件,這樣做可以減少無效資源的加載,明顯減少服務器的壓力和流量,也能夠減小瀏覽器的負擔。
實現(xiàn):結合 Vue 的異步組件和 Webpack 的代碼分割功能文章來源:http://www.zghlxwxcb.cn/news/detail-488990.html
const Foo = () => import('./Foo.vue')
router = new VueRouter({ routes: [ { path: '/foo', component: Foo } ]})
(圖片 素材都來源網絡,侵權聯(lián)系立馬刪)文章來源地址http://www.zghlxwxcb.cn/news/detail-488990.html
到了這里,關于性能優(yōu)化之-更高效的數據渲染的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!