本文主要是兩方面內(nèi)容:
淺析由瀏覽器內(nèi)核想到的前端優(yōu)化,或者說(shuō)前端優(yōu)化規(guī)則是從哪兒來(lái)的。
大家知道,大部分的 WEB 頁(yè)面依托瀏覽器呈現(xiàn),而瀏覽器能夠?qū)㈨?yè)面展示出來(lái),基本依賴于瀏覽器的內(nèi)核,即渲染引擎。今天以 Chrome 瀏覽器的內(nèi)核 WebKit(更確切是 WebKit 分支 Blink,以下統(tǒng)稱為 WebKit )為例,對(duì)渲染引擎如何展示頁(yè)面做個(gè)簡(jiǎn)單、全面的了解。
渲染引擎主要是將 WEB 資源如 HTML、CSS、圖片、JavaScript等經(jīng)過(guò)一系列加工,最終呈現(xiàn)出展示的圖像。渲染引擎主要包含了對(duì)這些資源解析的處理器,如 HTML 解釋器、CSS 解釋器、布局計(jì)算+繪圖工具、JavaScript 引擎等。為了更好地呈現(xiàn)渲染效果,渲染引擎還會(huì)依賴網(wǎng)絡(luò)棧、緩存機(jī)制、繪圖工具、硬件加速機(jī)制等。
瀏覽器的渲染過(guò)程,主要包括兩大部分:網(wǎng)頁(yè)資源加載過(guò)程和渲染過(guò)程。
上圖將整個(gè)網(wǎng)頁(yè)渲染的過(guò)程做了大致的剖析。以下我們按照數(shù)據(jù)流向,逐一詳細(xì)剖析每個(gè)過(guò)程。
當(dāng)我們?cè)跒g覽器中輸入 URL 后,瀏覽器首先會(huì)進(jìn)行域名解析。一般情況下,一次 DNS 域名解析大概需要 60-120 ms,一次 TCP 的三次握手需要 1.5 個(gè) RTT(round-trip time)。WebKit 的方案是 采用 DNS 預(yù)取技術(shù)和 TCP 預(yù)連接技術(shù)。
DNS 預(yù)取技術(shù)利用現(xiàn)有 DNS 機(jī)制,提前解析網(wǎng)頁(yè)中可能的網(wǎng)絡(luò)連接。即對(duì)用戶瀏覽網(wǎng)頁(yè)中存在的鏈接,用較少的 CPU 和網(wǎng)絡(luò)帶寬來(lái)解析這些鏈接的域名或 IP 地址;等用戶單擊鏈接時(shí),就會(huì)節(jié)省時(shí)間~ 特別是域名解析慢的時(shí)候~
同樣,在地址欄輸入鏈接時(shí),候選項(xiàng)也會(huì)被默默地執(zhí)行 DNS 預(yù)取~。在 DNS 預(yù)取后,會(huì)預(yù)先建立 TCP 連接。
對(duì)此前端優(yōu)化建議:
大數(shù)據(jù)分析,推測(cè)用戶可能點(diǎn)擊的鏈接,提前預(yù)取。
減少頁(yè)面中的域名數(shù)量,可以直接減少DNS的請(qǐng)求。
因?yàn)檎?qǐng)求帶來(lái)的 TCP 三次握手的 1.5 RTT 延遲,Google 引入 SPDY,嘗試解決HTTP的延遲和安全性(HTTP 明文方式)問(wèn)題。不過(guò),SPDY 促使了 HTTP2.0 的誕生后,自己也不再更新,逐步退出。
SPDY 基于 SSL 之上,輕松兼容 HTTP 新老版本。其優(yōu)勢(shì)如下:
不同資源,不同優(yōu)先級(jí)。比如優(yōu)先加載首屏。
Header 頭壓縮。減少傳送的字節(jié)數(shù)。SPDY 對(duì) Header 壓縮率可高達(dá) 80%。
SPDY 開(kāi)拓了 HTTP 新局面,秒殺我們太多的前端優(yōu)化工作,從本質(zhì)上提升了頁(yè)面加載速度。但我們前端優(yōu)化的工作還是不能偏廢。向著繼續(xù)減少請(qǐng)求,減少 TCP 連接建立的路上,讓我們繼續(xù)。
當(dāng)頁(yè)面資源較小時(shí),可直接放頁(yè)面中,如小圖可使用 Base64 編碼格式引入。甚至一些基礎(chǔ)樣式,或首屏依賴樣式,都可以放在頁(yè)面中;
資源壓縮技術(shù)。如 Gzip 等。主要是對(duì)響應(yīng)數(shù)據(jù)的壓縮~
精簡(jiǎn) JavaScript 和 CSS 代碼。減少無(wú)用的空格。壓縮混淆~
避免鏈接重定向、避免錯(cuò)誤的鏈接請(qǐng)求。建立多次鏈接、多次 DNS 解析,阻礙 DNS 預(yù)取技術(shù)。及時(shí)更新掉你頁(yè)面中沒(méi)有價(jià)值的鏈接吧。
域名解析完,TCP 連接也建立起來(lái)后,資源加載器就開(kāi)始工作了。
資源包括:HTML、JavaScript、CSS 樣式表、圖片、SVG、字體文件、視頻音頻等。資源加載器有三種:
緩存機(jī)制的資源加載器。特定加載器通過(guò)它查找是否有緩存資源,屬于 HTML 的文檔對(duì)象。
通用的資源加載器。在WebKit需要從網(wǎng)絡(luò)或文件系統(tǒng)獲取資源時(shí)使用。只負(fù)責(zé)獲取資源的數(shù)據(jù),被所有特定資源加載器共享。
在 WebKit 中,資源都以 CachedResource 為基類,以 Cached 為前綴,體現(xiàn)了瀏覽器的緩存機(jī)制。即請(qǐng)求資源時(shí),瀏覽器會(huì)先看緩存中有沒(méi)有這個(gè)資源,然后再?zèng)Q定是否向服務(wù)器發(fā)出請(qǐng)求。
這引出兩個(gè)問(wèn)題,首先,緩存資源的生命周期。
瀏覽器緩存不會(huì)無(wú)限增大,緩存池中的數(shù)據(jù)必然出現(xiàn)更替,WebKit 采用 LRU 最近最少使用算法更新緩存池?cái)?shù)據(jù)。WebKit 遵循 HTTP 協(xié)議,當(dāng)頁(yè)面刷新時(shí),判斷資源是否在資源池。若存在,則附上該資源在本地的一些信息(如修改時(shí)間等),發(fā)送 HTTP 請(qǐng)求給服務(wù)器,服務(wù)器根據(jù)信息作出判斷,若資源沒(méi)更新則網(wǎng)絡(luò)狀態(tài)為 304,利用現(xiàn)有資源;否則執(zhí)行資源加載過(guò)程。
其次,資源加載過(guò)程。
資源池中沒(méi)有該資源時(shí),執(zhí)行加載過(guò)程。WebKit 可以并行(多線程)下載普通資源和 JavaScript 資源。在當(dāng)前主線程被阻塞時(shí),WebKit 會(huì)啟動(dòng)另一個(gè)線程去遍歷后邊的網(wǎng)頁(yè),收集需要的資源 URL再發(fā)請(qǐng)求,避免阻塞。
基于資源加載,前端優(yōu)化建議:
比如設(shè)置 ETag/Last-Modified 和 Expires/Cache-Control。
Expires/Cache-Control 兩者作用一致,指明資源有效期,如果本地緩存還在有效期內(nèi),瀏覽器直接使用本地緩存,不再發(fā)送請(qǐng)求。兩者同時(shí)配置時(shí),Cache-Control 高于 Expires。配置 ETag/Last-Modified 后,瀏覽器再次訪問(wèn) URL 時(shí),還會(huì)向服務(wù)器發(fā)送請(qǐng)求,確認(rèn)文件是否已修改,沒(méi)修改則服務(wù)器返回304,瀏覽器直接從本地緩存獲取數(shù)據(jù);修改過(guò)則服務(wù)器返回?cái)?shù)據(jù)給瀏覽器。兩者同時(shí)配置,服務(wù)器會(huì)優(yōu)先檢測(cè) ETag,一致才會(huì)繼續(xù)檢測(cè) Last-Modified。兩者同時(shí)配置,可以使服務(wù)器更準(zhǔn)確的判斷瀏覽器是否已有需要的緩存數(shù)據(jù)。
ETag/Last-Modified 和 Expires/Cache-Control 兩對(duì)都設(shè)置時(shí), Expires/Cache-Control 優(yōu)先級(jí)更高。所以,只要本地緩存在有效期內(nèi),就不會(huì)發(fā)送請(qǐng)求。但頁(yè)面 F5 刷新和強(qiáng)刷時(shí),緩存將失效。
當(dāng)我們拿到頁(yè)面所需的資源后,渲染引擎便啟動(dòng) HTML 解釋器,對(duì)獲取的資源進(jìn)行解析處理。網(wǎng)頁(yè)代碼(字節(jié)流)經(jīng)過(guò)詞法分析器解碼,再由語(yǔ)法分析器解釋成詞語(yǔ) Token,并構(gòu)建成節(jié)點(diǎn) Node,直到最終構(gòu)建成一棵 DOM 樹(shù)。
期間,當(dāng)節(jié)點(diǎn)為 JavaScript 節(jié)點(diǎn)時(shí),將啟動(dòng) JavaScript 引擎,這時(shí)將阻塞 DOM 樹(shù)的構(gòu)建。因?yàn)?JavaScript 執(zhí)行過(guò)程中, JavaScript 很可能會(huì)對(duì) DOM 樹(shù)進(jìn)行讀寫操作。直到 JavaScript 執(zhí)行完畢, DOM 樹(shù)才會(huì)恢復(fù)構(gòu)建。
其他資源并不影響 DOM 樹(shù)的構(gòu)建。
在前端優(yōu)化中,建議將 CSS 文件放在頁(yè)首,以便構(gòu)建 DOM 樹(shù);而將 JavaScript 文件盡量放在頁(yè)面下方,防止阻塞構(gòu)建 DOM 樹(shù);而 JavaScript 的 onload 事件里,不要寫太多影響首屏渲染的、操作 DOM 樹(shù)的 JavaScript 代碼。
另外強(qiáng)調(diào)一下:
DOMContentLoaded: DOM 樹(shù)構(gòu)建完;
DOM 的onload事件: DOM 樹(shù)構(gòu)建完且網(wǎng)頁(yè)依賴的資源都加載完了~
這一過(guò)程,就像是頁(yè)面的排版過(guò)程。它通過(guò) CSS 樣式信息,對(duì) DOM 樹(shù)進(jìn)行排版,形成 RenderObject 樹(shù)及 RenderLayer 樹(shù)。
在 DOM 樹(shù)構(gòu)建完成后,WebKit 為 DOM 樹(shù)節(jié)點(diǎn)構(gòu)建 RenderObject 對(duì)象。WebKit 將根據(jù)盒模型計(jì)算節(jié)點(diǎn)的位置、大小等樣式信息(即布局計(jì)算或排版),并將這些信息保存到對(duì)應(yīng)的 RenderObject 對(duì)象。
CSS解釋過(guò)程,是從 CSS 字符串經(jīng)過(guò) CSS 解釋器(CSSParser、CSSGrammer)處理后,變成渲染引擎的內(nèi)部樣式規(guī)則表示的過(guò)程。樣式規(guī)則是解釋器的輸出結(jié)構(gòu),是樣式匹配的輸入數(shù)據(jù)。
具體過(guò)程:WebKit 在渲染元素時(shí),CSS 解釋器獲取樣式信息,返回匹配好的結(jié)果樣式信息。每個(gè)元素可能需要匹配不同來(lái)源的規(guī)則,依次是用戶代理(瀏覽器)規(guī)則集合、用戶規(guī)則集合和HTML頁(yè)面中包含的自定義規(guī)則集合。三者匹配方式類似。
對(duì)于每個(gè)規(guī)則集合,先查找 ID 規(guī)則,檢查有無(wú)匹配的規(guī)則,然后依次檢查類型規(guī)則、標(biāo)簽規(guī)則等。匹配好的規(guī)則,保存到匹配結(jié)果中。WebKit 對(duì)這些規(guī)則進(jìn)行排序。對(duì)于元素需要的樣式屬性,WebKit 選擇從高優(yōu)先級(jí)規(guī)則中選取,并將樣式屬性值返回。
DOM 樹(shù)經(jīng)過(guò)布局計(jì)算、CSS parse 后,將樣式信息存儲(chǔ)在 RenderObject 對(duì)象中,并構(gòu)建成 RenderObject 樹(shù)。同時(shí),WebKit 會(huì)根據(jù)網(wǎng)頁(yè)的層次結(jié)構(gòu)創(chuàng)建 RenderLayer 樹(shù),完成繪圖上下文。DOM 樹(shù)、Render 樹(shù)和繪圖上下文同時(shí)并存,直到頁(yè)面銷毀。
RenderObject 樹(shù),基于 DOM 樹(shù)的一棵新樹(shù),是布局計(jì)算和渲染等機(jī)制的基礎(chǔ)設(shè)施。
DOM 節(jié)點(diǎn)建立新的 RenderObject 對(duì)象的時(shí)機(jī):
DOM 樹(shù)的可視節(jié)點(diǎn),如html、body、div 等。非可視節(jié)點(diǎn)如meta、head、script 等不創(chuàng)建。
為滿足 WebKit 處理,需要建立匿名 RenderObject 節(jié)點(diǎn),它不對(duì)應(yīng)于 DOM 樹(shù)的任何節(jié)點(diǎn)。如:匿名的 RenderBlock 節(jié)點(diǎn)。
DOM 樹(shù)的每個(gè)節(jié)點(diǎn)對(duì)象會(huì)遞歸檢查是否需要?jiǎng)?chuàng)建 RenderObject,并根據(jù) DOM 節(jié)點(diǎn)類型創(chuàng)建 RenderObject 節(jié)點(diǎn);動(dòng)態(tài)加入的 DOM 元素,會(huì)相應(yīng)的創(chuàng)建 RenderObject 節(jié)點(diǎn)。所有這些節(jié)點(diǎn)構(gòu)成一棵 RenderObject 樹(shù)。
在 HTML 頁(yè)面上,網(wǎng)頁(yè)分層展示。目的有兩個(gè):1. 方便開(kāi)發(fā)網(wǎng)頁(yè)、設(shè)置網(wǎng)頁(yè)的層次;2. 簡(jiǎn)化 WebKit 渲染的邏輯。
在RenderObject 樹(shù)基礎(chǔ)上,WebKit 根據(jù)需要為其中的某些節(jié)點(diǎn)創(chuàng)建新的 RenderLayer 節(jié)點(diǎn),并形成一棵 RenderLayer 樹(shù)。
RenderObject 節(jié)點(diǎn)建立新 RenderLayer 對(duì)象的時(shí)機(jī):
DOM 樹(shù)的 Document 的子節(jié)點(diǎn),即 HTML 節(jié)點(diǎn)對(duì)應(yīng)的 RenderBlock 節(jié)點(diǎn)。
顯式的指定 CSS 位置的 RenderObject 節(jié)點(diǎn)。
有透明效果的 RenderObject 節(jié)點(diǎn)。
節(jié)點(diǎn)有溢出 overflow、alpha 或反射等效果的 RenderObject 節(jié)點(diǎn)。
使用Canvas 2D、3D (WebGL)技術(shù)的 RenderObject 節(jié)點(diǎn)。
Video 節(jié)點(diǎn)對(duì)應(yīng)的 RenderObject 節(jié)點(diǎn)。
RenderLayer 節(jié)點(diǎn)的使用可以有效減少網(wǎng)頁(yè)結(jié)構(gòu)的復(fù)雜程度,并在許多情況下能減少重新渲染的開(kāi)銷。
CSS 盒模型,是布局計(jì)算的基礎(chǔ);渲染引擎用來(lái)確定如何排版元素、及元素間的位置關(guān)系。
布局計(jì)算,是針對(duì) RenderObject 樹(shù)及其子樹(shù)的計(jì)算,是一種遞歸計(jì)算,其節(jié)點(diǎn)信息需要先計(jì)算其子節(jié)點(diǎn)的位置、大小等信息。RenderObject 對(duì)象會(huì)將計(jì)算結(jié)果存儲(chǔ),等待渲染時(shí)機(jī)。
重繪時(shí)機(jī):只要樣式發(fā)生變化,就重新計(jì)算。
網(wǎng)頁(yè)的動(dòng)畫會(huì)觸發(fā)布局計(jì)算。動(dòng)畫可能改變樣式屬性。
JavaScript 通過(guò) CSSOM(CSS 對(duì)象模型) 直接修改樣式,會(huì)觸發(fā) WebKit 重新計(jì)算布局。
用戶交互,如滾動(dòng)網(wǎng)頁(yè)。
前端優(yōu)化建議,因布局計(jì)算耗時(shí)間,一旦布局發(fā)生變化,WebKit 就需要后面的重新繪制操作。SO,減少樣式的變動(dòng)~減少重繪~利用 CSS3 新功能(如 CSS3 變形 translate、scale、rotate 等方法,過(guò)渡 transition 方法等)可有效提高網(wǎng)頁(yè)的渲染效率。
在上一個(gè)過(guò)程,網(wǎng)頁(yè)完成了 DOM 樹(shù)到 RenderLayer 樹(shù)的布局計(jì)算和排版處理。接下來(lái),由渲染引擎(一般是繪圖類工具)完成對(duì) RenderLayer 樹(shù)的繪制,并最終形成圖像,展示給用戶。
繪圖上下文,所有的繪圖操作都是在該上下文中進(jìn)行的。它是一個(gè)與平臺(tái)無(wú)關(guān)的抽象類,它將每個(gè)繪圖操作橋接到不同的繪圖具體實(shí)現(xiàn)類。
2D 繪圖上下文:
繪圖接口包括:畫點(diǎn)、畫線、畫圖、畫多邊形、畫文字等。繪圖樣式包括顏色、線寬、字號(hào)、漸變等。
CPU 來(lái)完成 2D 操作?;蛴?3D 圖形接口( OpenGL )完成。
3D 繪圖上下文:支持 CSS3D、WebGL 等。
軟件渲染:CPU。通常渲染的結(jié)果是一個(gè)位圖,繪制每一層時(shí)都使用該位圖,區(qū)別在于位置可能不同,每一層按從后到前的順序。沒(méi)必要為每層分配一個(gè)位圖,沒(méi)必要合成。
缺點(diǎn):對(duì) HTML5 新技術(shù),
性能不好,如視頻、Canvas 2D;
使用率下降,特別是移動(dòng)端。
優(yōu)勢(shì):對(duì)更新區(qū)域處理,軟件渲染可能只需要計(jì)算極小區(qū)域,硬件則需要繪制其中一層或多層,再合成。硬件代價(jià)大。
硬件加速渲染:GPU 必須有合成的步驟。分層繪制+合成。不過(guò)對(duì)于更新區(qū)域,如果只是在一個(gè)層,硬件可能會(huì)更快。
WebKit 的實(shí)現(xiàn)方式:
使用CSS 3D 變形和動(dòng)畫技術(shù)。CSS 3D 變形技術(shù),能讓瀏覽器僅使用合成器合成所有層就可以達(dá)到動(dòng)畫效果。不需要布局計(jì)算和重繪~
前端優(yōu)化建議:
至此,從輸入 URL 到頁(yè)面呈現(xiàn),我們大致做了介紹。但這只是皮毛最上方的一點(diǎn),更多瀏覽器內(nèi)核的實(shí)質(zhì),值得我們下載一份源碼,編譯解析深挖~ 相信在前端優(yōu)化的路上,知其然,知其所以然~ 定會(huì)走得跟遠(yuǎn)~~