摘要:縮短的長(zhǎng)度能夠有效降低首屏?xí)r間。即便你使用打包工具只引用了一個(gè)外部腳本文件,但是如果這個(gè)腳本文件的傳輸延遲和執(zhí)行延遲,會(huì)導(dǎo)致后面的非關(guān)鍵資源的請(qǐng)求被延遲,雖然這不會(huì)減慢的首屏?xí)r間。
以通俗的方式理解關(guān)鍵渲染路徑
1. 什么是 CRP ?我在看了 google 的 Critical Rendering Path (中文)后, 想把 CRP(Critical Rendering Path) 用通俗易懂的方式描述出來(lái)。 官方文檔當(dāng)然是描述最為詳盡且可靠的。 文章里的有些圖片是直接引用自官方文檔。 如果存在侵權(quán), 立刻刪除。
游覽器從開(kāi)始請(qǐng)求 HTML 文檔, 到首次渲染到屏幕上(首屏), 背后需要做很多的事情, 這一連串事情就是 CRP 。 開(kāi)發(fā) app 的時(shí)候很多優(yōu)化都是和縮短 CRP 有關(guān)的。 縮短 CRP 的長(zhǎng)度能夠有效降低首屏?xí)r間。 這也是理解 CRP 最大的好處。
CRP 大概包括兩個(gè)部分: 1、 本地加載渲染, 2、 網(wǎng)絡(luò)請(qǐng)求 。
2. 游覽器的渲染原理從游覽器加載 HTML 文檔到首屏, 中間發(fā)生了什么?
TL;DR
游覽器解析 HTML 形成 DOM 樹(shù)。
游覽器解析 CSS 代碼輸出 CSSOM 。
DOM 和 CSSOM 一起構(gòu)造出一顆 Render Tree 。
游覽器對(duì) Render Tree 上的節(jié)點(diǎn)計(jì)算精確位置(布局)。
使用 Render Tree 繪制 (Painting) 到屏幕上。
DOM 用來(lái)描述文檔結(jié)構(gòu), 因而是樹(shù)形結(jié)構(gòu)。 getElementById , querySelector 等 API 就是針對(duì)DOM操作的。 CSSOM 用來(lái)描繪 DOM 上的節(jié)點(diǎn)的樣式, 所以圖中的 CSSOM 的根節(jié)點(diǎn)是 body , 這是因?yàn)?head 標(biāo)簽沒(méi)有樣式信息。 樣式的繼承過(guò)程就是發(fā)生在構(gòu)造 CSSOM 的階段。 當(dāng)你使用 element.style 訪(fǎng)問(wèn)元素的樣式的時(shí)候, 其實(shí)訪(fǎng)問(wèn)的是CSSOM 上對(duì)應(yīng)的節(jié)點(diǎn)。 然后利用 DOM 和 CSSOM 生成 Render Tree , 這中間做了很多事情, 比如說(shuō)如果檢測(cè)到一個(gè)元素所對(duì)應(yīng)的 CSSOM 節(jié)點(diǎn)存在 display: none , 那這個(gè)節(jié)點(diǎn)將不會(huì)輸出到 Render Tree 。
接下來(lái)要利用 Render Tree 計(jì)算每個(gè)節(jié)點(diǎn)在視口上具體的位置, 這就是布局。 布局階段輸出 CSS 的 “盒子模型”, 最后利用這些盒子, 繪制到屏幕上 (Painting) , 在這個(gè)過(guò)程中如果檢測(cè)到 visibility: hidden , 游覽器會(huì)將其繪制成一個(gè)空白(這里的空白是完完全全的空白, 而不是白色。。。)
現(xiàn)在對(duì)于 visibility: hidden 和 display: none 的理解應(yīng)該加深了不少, 具體的區(qū)別可以 google 。
這就是游覽器的大概的渲染過(guò)程。
3. 深入 CRP其實(shí)討論 CRP 更多的時(shí)候是在討 CSS 和 JS 。 因?yàn)?CSS 和 JS 會(huì)阻塞渲染。 為什么說(shuō) CSS 和 JS 會(huì)阻塞渲染? 很難想象如果游覽器先呈現(xiàn)一段沒(méi)有樣式的頁(yè)面,然后突然刷新, 這是一種及其糟糕的體驗(yàn)。 而 JS 腳本的執(zhí)行會(huì)訪(fǎng)問(wèn) DOM 和 CSSOM, 為什么 JS 是同步加載, 而不是異步加載呢? 游覽器為什么不像處理樣式文件一樣處理腳本文件呢? 這其實(shí)很好理解, 腳本文件一般包含著你應(yīng)用的邏輯, 如果腳本都是異步加載, 那應(yīng)用的邏輯豈不是亂套。。
什么是 CRP 長(zhǎng)度?
獲取所有阻塞資源(關(guān)鍵資源)所需的往返次數(shù), 比如說(shuō)樣式文件, 腳本文件; 圖片不屬于關(guān)鍵資源, 因?yàn)閳D片不會(huì)導(dǎo)致阻塞游覽器渲染。
關(guān)于 CRP 中幾個(gè)關(guān)鍵的時(shí)間點(diǎn):
domLoading 這是整個(gè) CRP 的開(kāi)始的時(shí)間點(diǎn)。
domInteractive 游覽器剛好構(gòu)建完 DOM 的時(shí)間點(diǎn)。
domContentLoaded DOM 構(gòu)建完成, 且沒(méi)有任何樣式會(huì)阻塞腳本允許的時(shí)間點(diǎn)。 意思就是當(dāng)沒(méi)有腳本文件執(zhí)行的時(shí)候, DOM 構(gòu)建完成時(shí)就到達(dá)這個(gè)時(shí)間點(diǎn), 畢竟沒(méi)有腳本需要執(zhí)行怎么會(huì)存在阻塞呢? 不過(guò)這種情況很少見(jiàn)。 當(dāng)有腳本文件要執(zhí)行的時(shí)候, 樣式( CSSOM )會(huì)阻塞腳本文件執(zhí)行, 此時(shí)要等到樣式全部就緒的時(shí)候才會(huì)到達(dá)這個(gè)時(shí)間點(diǎn)。 所以, 當(dāng)存在腳本文件的時(shí)候, 一般 domContentLoaded 會(huì)往后推移許多。
domCompelete 表示所有資源都已經(jīng)下載完成, 包括圖片, 字體等。 這個(gè)時(shí)間點(diǎn)將觸發(fā) onload 事件。
如圖:
talk is cheap show me the code
HTML 文件:
Test
這段代碼首先在 head 里引用了樣式文件, 在文檔的最后引用了 script 文件, 這是因?yàn)?script 標(biāo)簽會(huì)導(dǎo)致游覽器阻塞 DOM 的解析, 游覽器甚至?xí)〞r(shí)間等待 script 引用的腳本資源的請(qǐng)求過(guò)程(同步加載), 直至請(qǐng)求響應(yīng)后且解析完腳本后才會(huì)將控制權(quán)交還給游覽器來(lái)繼續(xù)解析 DOM 。 因此腳本執(zhí)行時(shí), 很可能 DOM 沒(méi)有創(chuàng)建完成, 將 script 放到最后是明智之舉。 這里一共請(qǐng)求了三個(gè)資源, 但是幾乎都是同時(shí)發(fā)起的, 因此算作一次往返, 所以 CRP 長(zhǎng)度是1。
使用 DevTools 的 Timeline 查看頁(yè)面加載情況。 如果你對(duì) DevTools 不熟悉, 可以看下 Chrome DevTools 快速入門(mén) 的官網(wǎng)文檔 。 實(shí)際上這里并用不到很多這方面的知識(shí)。 稍微有所了解就行。 在使用 Timeline 的時(shí)候要注意開(kāi)啟隱身模式, 不然會(huì)存在諸如游覽器插件等干擾因素。
上面的代碼的加載性能截圖:
可以看到圖中有兩條非常接近的垂直的線(xiàn), 藍(lán)色表示觸發(fā) DOMContentLoaded 事件的時(shí)間點(diǎn), 紅色表示觸發(fā) onload 事件的時(shí)間點(diǎn)。兩個(gè)時(shí)間點(diǎn)非常接近, 這是由于游覽器在請(qǐng)求外部腳本時(shí)會(huì)等待其響應(yīng), 因此將 DOMContentLoaded 事件向后推遲了。 這里的 CRP 長(zhǎng)度是1。 假如沒(méi)有腳本文件的請(qǐng)求, 那我們應(yīng)用的 DOMContentLoaded 將在 DOM 解析完成后發(fā)生, 而不至于等到所有樣式就緒( CSS 解析完成)。 這里即便是將腳本文件內(nèi)聯(lián)到 HTML 文檔中也是一樣的結(jié)果。 因?yàn)槟_本代碼的執(zhí)行會(huì)訪(fǎng)問(wèn) CSSOM , 游覽器在解析腳本代碼的時(shí)候會(huì)等待所有 CSSOM 就緒才開(kāi)始執(zhí)行腳本代碼。 因此效果是一樣的。
這是上面代碼的 CRP 流程圖:
可以看到在 T1 到 T2 這個(gè)時(shí)間段, 游覽器解析 DOM 的操作是被阻塞的。 即便內(nèi)聯(lián)了腳本文件。
因?yàn)榇蠖鄶?shù)的 JavaScript 框架的邏輯開(kāi)始部分都是從 DOMContentLoaded 事件點(diǎn)開(kāi)始的(因?yàn)?DOMContentLoaded 總比 onload 快, onload 會(huì)等到圖片等資源都就緒才觸發(fā), 會(huì)拖慢首屏?xí)r間)。 因此, 提前 DOMContentLoaded 是很有必要的。
一般的 CRP 流程是這樣的:
圖上有兩個(gè)灰色的方塊, 這兩個(gè)方塊是導(dǎo)致 CRP 時(shí)間變長(zhǎng)的罪魁禍?zhǔn)祝?/p> 4. 減少 CRP , 出發(fā)!
縮短 CRP 就是盡快使 DOMContentLoaded 事件產(chǎn)生, 以便盡快執(zhí)行 APP 邏輯, 不要拖到 onload , 節(jié)約一分一秒! 因?yàn)?DOMContentLoaded 產(chǎn)生后, 就會(huì)構(gòu)建 Render Tree , 然后順?biāo)浦郏?用戶(hù)就能看到 APP 了。
不要引用過(guò)多的腳本文件!
在前幾年的前端開(kāi)發(fā)中, 喜歡將對(duì)腳本的引用寫(xiě)在 HTML 文件里, 這會(huì)導(dǎo)致 DOMContentLoaded 觸發(fā)大大延遲。 因?yàn)槊恳淮谓馕鲆粋€(gè)腳本都會(huì)阻塞游覽器構(gòu)建 DOM 。 當(dāng)一個(gè) HTML 文件引用幾十個(gè)外部腳本文件時(shí), 那肯定是一場(chǎng)災(zāi)難! 所幸最近幾年各種前端打包工具使得這個(gè)問(wèn)題得以解決。
在筆試騰訊的時(shí)候遇到一個(gè)問(wèn)題: 使用 HTTP2 的時(shí)候還有沒(méi)有將 JS 文件打包的必要? 答案是: 有必要, 而且是必須的! 雖然 HTTP2 可以實(shí)現(xiàn)同一個(gè) TCP 連接里的所有資源的并行傳輸(使用流)。 但是游覽器處理外部腳本文件的策略不會(huì)變! 所以依然有打包的必要!
將外部腳本的引用放到 HTML 文件的最后, 或者使用 defer 屬性。
即便你使用打包工具只引用了一個(gè)外部腳本文件, 但是如果這個(gè)腳本文件的傳輸延遲和執(zhí)行延遲, 會(huì)導(dǎo)致后面的非關(guān)鍵資源的請(qǐng)求被延遲, 雖然這不會(huì)減慢 APP 的首屏?xí)r間。 但是圖片等非關(guān)鍵資源的呈現(xiàn)時(shí)間卻被延遲了。 使用defer屬性可以將執(zhí)行時(shí)間推遲到 domContentLoaded 時(shí)間點(diǎn)后。 DOMContentLoaded也是這個(gè)時(shí)間點(diǎn)后觸發(fā)。那會(huì)先執(zhí)行 defer 還是先觸發(fā) DOMContentLoaded 事件? 在 Chrome 游覽器下的順序是先執(zhí)行 defer 再觸發(fā)DOMContentLoaded 事件。
對(duì)外部腳本使用 async 屬性
async 屬性告訴游覽器異步請(qǐng)求外部腳本文件, 不要阻塞在這里。 這樣可以使得游覽器繼續(xù)構(gòu)建 DOM 。 或者處理后面的資源請(qǐng)求。
將樣式文件請(qǐng)求置于 head 標(biāo)簽內(nèi)。
盡早在 HTML 文檔內(nèi)指定所有 CSS 資源,以便瀏覽器盡早發(fā)現(xiàn) link 標(biāo)記并盡早發(fā)出 CSS 請(qǐng)求。
盡量不要使用 @import 指令。
CSS 中的 @import 表示在一個(gè)樣式文件中導(dǎo)入另外一個(gè)樣式文件。 一個(gè)樣式文件 import 另外一個(gè)樣式文件時(shí), 只有在這個(gè)樣式文件被收到且被解析完成才會(huì) import 。 這樣會(huì)增加 CRP 長(zhǎng)度。
內(nèi)聯(lián)樣式
使用 style 標(biāo)簽將樣式內(nèi)聯(lián)到 HTML 文件內(nèi), 雖然這樣做會(huì)增大 HTML 文件的體積。 但是卻可以減少 CRP 長(zhǎng)度。 同時(shí)也避免了腳本代碼的執(zhí)行被阻塞的情況。
為什么沒(méi)有內(nèi)聯(lián)腳本呢? 因?yàn)榻^大多數(shù)情況下不可能做到完全的內(nèi)聯(lián)樣式。 這個(gè)時(shí)候即便使用內(nèi)聯(lián)腳本也會(huì)因?yàn)?CSSOM 而被阻塞, 會(huì)阻止 DOM 構(gòu)建。 因而這并不是一種優(yōu)化措施。
減少你的關(guān)鍵資源的大小和數(shù)量
這是優(yōu)化最好的手段。
Else
這里并沒(méi)有提到如何去優(yōu)化應(yīng)用的邏輯來(lái)增加速度, 因?yàn)檫@并不屬于 CRP 。
5. 常用優(yōu)化工具安利。Lighthouse
Lighthouse 是一個(gè)網(wǎng)絡(luò)應(yīng)用審核工具, 可以幫助你找到 CRP 的瓶頸所在。
Lighthouse 的截圖:
Navigation Time API
這個(gè)內(nèi)置的 API 可以幫助你記錄下各個(gè)時(shí)間點(diǎn)的具體時(shí)間值, 上面說(shuō)的那幾個(gè)時(shí)間點(diǎn)都有。
6. 最后的最后如果你覺(jué)得有哪里寫(xiě)的不是很恰當(dāng)或你不能理解的, 可以在評(píng)論里告訴我。。
如果你覺(jué)得我寫(xiě)的內(nèi)容對(duì)你有所幫助。 可以選擇關(guān)注我。
我的博客地址是: mrcode
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/50650.html
摘要:縮短的長(zhǎng)度能夠有效降低首屏?xí)r間。即便你使用打包工具只引用了一個(gè)外部腳本文件,但是如果這個(gè)腳本文件的傳輸延遲和執(zhí)行延遲,會(huì)導(dǎo)致后面的非關(guān)鍵資源的請(qǐng)求被延遲,雖然這不會(huì)減慢的首屏?xí)r間。 以通俗的方式理解關(guān)鍵渲染路徑 我在看了 google 的 Critical Rendering Path (中文)后, 想把 CRP(Critical Rendering Path) 用通俗易懂的方式描述出...
摘要:可見(jiàn)對(duì)一個(gè)頁(yè)面正確渲染很重要。和標(biāo)簽對(duì)用于標(biāo)識(shí)頁(yè)面的頭部區(qū)域,和之間的內(nèi)容都屬于頭部區(qū)域中的內(nèi)容。是一個(gè)輔助性標(biāo)簽,對(duì)頁(yè)面可以進(jìn)行很多方面的特性的設(shè)置。當(dāng)頁(yè)面沒(méi)有設(shè)置字符集時(shí),瀏覽器會(huì)使用默認(rèn)的字符編碼顯示。 showImg(https://segmentfault.com/img/bVbjNkI?w=900&h=383); 往期回顧 在 1.2 節(jié)介紹 HTML 語(yǔ)言時(shí)講到:HTML...
摘要:客戶(hù)端發(fā)送包到服務(wù)器,并進(jìn)入狀態(tài),等待服務(wù)器確認(rèn)。再進(jìn)一步接收到客戶(hù)端的就進(jìn)入狀態(tài)。通常情況下連接就是連接,因此連接一旦建立通訊雙方開(kāi)始互發(fā)數(shù)據(jù)進(jìn)行通信,直到其中一方或雙方斷開(kāi)連接為止。統(tǒng)一資源定位符。 本文旨在用最通俗的語(yǔ)言講述最枯燥的基本知識(shí) 面試過(guò)前端的老鐵都知道,對(duì)于前端,面試官喜歡一開(kāi)始先問(wèn)些HTML5新增元素啊特性啊,或者是js閉包啊原型啊,或者是css垂直水平居中怎么實(shí)現(xiàn)...
摘要:客戶(hù)端發(fā)送包到服務(wù)器,并進(jìn)入狀態(tài),等待服務(wù)器確認(rèn)。再進(jìn)一步接收到客戶(hù)端的就進(jìn)入狀態(tài)。通常情況下連接就是連接,因此連接一旦建立通訊雙方開(kāi)始互發(fā)數(shù)據(jù)進(jìn)行通信,直到其中一方或雙方斷開(kāi)連接為止。統(tǒng)一資源定位符。 本文旨在用最通俗的語(yǔ)言講述最枯燥的基本知識(shí) 面試過(guò)前端的老鐵都知道,對(duì)于前端,面試官喜歡一開(kāi)始先問(wèn)些HTML5新增元素啊特性啊,或者是js閉包啊原型啊,或者是css垂直水平居中怎么實(shí)現(xiàn)...
摘要:客戶(hù)端發(fā)送包到服務(wù)器,并進(jìn)入狀態(tài),等待服務(wù)器確認(rèn)。再進(jìn)一步接收到客戶(hù)端的就進(jìn)入狀態(tài)。通常情況下連接就是連接,因此連接一旦建立通訊雙方開(kāi)始互發(fā)數(shù)據(jù)進(jìn)行通信,直到其中一方或雙方斷開(kāi)連接為止。統(tǒng)一資源定位符。 本文旨在用最通俗的語(yǔ)言講述最枯燥的基本知識(shí) 面試過(guò)前端的老鐵都知道,對(duì)于前端,面試官喜歡一開(kāi)始先問(wèn)些HTML5新增元素啊特性啊,或者是js閉包啊原型啊,或者是css垂直水平居中怎么實(shí)現(xiàn)...
閱讀 2877·2021-11-16 11:55
閱讀 2628·2021-09-29 09:34
閱讀 3446·2021-09-01 14:21
閱讀 3781·2019-08-29 12:36
閱讀 706·2019-08-26 10:55
閱讀 3997·2019-08-26 10:20
閱讀 1039·2019-08-23 18:19
閱讀 1205·2019-08-23 17:56