從輸入url開始能做哪些優化
文主要講的事情是如何讓用戶快點看到首屏頁面,其主要影響因素是延遲和解析渲染耗時。有關安全部分其實也是優化的一部分。我們著重說下網絡部分。
大致過程:DNS域名解析、建立TCP連接、下載資源、解析頁面。文章描述的優化會盡量限制在當時的分析的過程下。
1.DNS域名解析
一般來講,我們輸入的url是域名,而為了識別一個實體,TCP/IP使用IP地址來唯一確定一臺主機到因特網的連接,DNS會幫助我們完成域名到IP地址映射的工作。以www.aaa.com為例,解析過程大致如下:
過程
- 瀏覽器
- 瀏覽器查詢瀏覽器緩存,沒有。
- 本機層
- 瀏覽器客戶端向系統詢問服務器IP地址,調用本機內的DNS解析程序,檢查自己本地的hosts文件是否有這個域名映射關系,沒有。
- 查找本機的DNS解析器緩存,沒有。
- 路由器緩存
- 可能還存在路由器緩存這一層
- 本地DNS服務器
- 本機的DNS解析程序向本地的DNS服務器發起請求,一般為TCP/IP參數中設置的首選DNS服務器,是知道IP地址的,一般會UDP協議。
- 本地DNS服務器查詢是否在本地區域文件中,沒有。
- 本地DNS服務器查詢DNS緩存中是否存在,沒有。
- 本地DNS服務器會根據是否設置轉發器判斷是向上一級DNS服務器(其解析規則同理)還是直接向根DNS服務器(知道根DNS服務器的IP地址)發送請求。
- 與DNS服務器
- 收到請求后,根DNS服務器并不直接解析地址,但是知道每個頂級域中的一臺服務器的地址(如com域名服務器)。如果為迭代查詢方式,此頂級域DNS服務器的ip被返回給本地DNS服務器。
- 本地DNS服務器提取到頂級域DNS服務器信息后,會再向其發出請求。頂級域DNS服務器收到請求后,會先查詢自己的緩存,沒有,則將負責的二級域名服務器(如aaa.com域名服務器)返回給本地DNS服務器,以此類推直到查到目標域名的映射信息或查詢失敗。
- 查到映射信息后返回到本機,中間各層會進行緩存。
- 查詢方式:
- 遞歸方式:一路查下去中間不返回,得到最終結果才返回信息。
- 迭代方式:就是上面的本地DNS服務器與其他域名服務器直接的查詢方式,查到一個可能知道的服務器地址,將此地址返回,重新發送解析請求。
- 一般默認的方式從本機到本地DNS服務器是遞歸,DNS服務器之間是迭代查詢。
優化
當然針對DNS的優化就是減少DNS解析的時間,由于瀏覽器緩存機制的存在,我們只需要對首次訪問進行優化(雖然我們現在只是請求了一個html文件,但是html文件里還會有我們后續要請求的css/js/img等),即適當減少要解析的域名個數,考慮到其他優化機制可以將頁面及頁面內資源發布到2-4個域名上。
2.建立連接
TCP連接
好了,瀏覽器終于拿到服務器IP了,客戶端想要與服務器間通信并傳遞消息需要開啟TCP(一種傳輸層協議)連接。
過程
- 客戶端創建socket,向服務器目標端口發送連接建立請求,數據段包含位碼SYN(建立聯機標志位)=1,隨機數seq(順序號碼)=x,和其他TCP標志和選項。
- 服務器有一個專門處理連接請求的welcome socket,接收到連接建立請求,置位碼SYN和ACK(確認標志位)為1,ack(確認號碼)=x + 1,隨機數seq=y,并返回。
- 客戶端檢查ack是否等于x + 1,等于時,將ACK置為1,SYN置為0,將ack置為y + 1發送至服務器端。
- welcome socket檢查ack等于y + 1和ACK等于1后,創建新的socket,此socket由源IP/源端口、目標IP/目標端口標識,之后客戶端發送的數據都被引導向此新的socket,至此,TCP連接建立。
簡單來講:
// client:
send({SYN: 1, seq: x, ...others})
|
↓
//server:
send({SYN: 1, ACK: 1, ack: x + 1, seq: y, ...others})
|
↓
//client:
ack===x + 1 ? send({ACK: 1, SYN: 0, ack: y + 1, ...others}) : 'hehe'
|
↓
//server:
ack===y + 1 && ACK===1 ? new Socket() : ''
SSL/TLS
如果啟用了HTTPS進行加密,在使用TLS前還需要協商建立加密信道。
過程
- 客戶端:TCP連接建立之后,再以純文本形式發送一些規格說明,隨機數Random1,TLS協議版本,支持的加密套件列表,支持或希望使用的其他TLS選項。
- 服務器:
- 取得TLS協議版本以備將來通信使用,從客戶端提供的加密套件列表中選擇一個,生成隨機數Random2發送給客戶端;
- 附上自己的證書,將響應發送給客戶端;
- 同時,也可發送一個請求,要求客戶端提供證書以及其他TLS擴展參數。
- 同上,可能會向服務器發送自己的證書。
- 客戶端收到服務器的證書后,通過證書鏈關系從根CA(證書的簽發機構)驗證證書的合法性,驗證通過后取出證書中的服務器公鑰,生成隨機數Random3,再用服務器公鑰加密Random3(pre master key),發送給服務器;
- 告訴服務器可以開始加密透明信了;
- 客戶端用三個隨機數和約定的加密方法生成對話密鑰。將前面的握手信息生成完成摘要,使用對話密鑰加密,發送告訴服務器我已完成握手。
- 除了服務器公鑰加密的新對稱密鑰外,所有的數據都是明文形式發送。
- 服務器:
- 用私鑰解密出客戶端發來的隨機數,通過驗證消息的MAC檢測消息完整性,用相同的方式生成對話密鑰。
- 解密客戶端發送的完成報文,驗證對話密鑰是否正確。
- 告訴客戶端,要開始加密了;
- 同樣再返回給客戶端一個加密的完成消息。
- 客戶端用它之前生成的對話密鑰解密這條消息,確定對話密鑰是否正確,正確則建立信道并且開始發送應用數據。
其中:
- 對話密鑰又可稱為協商密鑰。
- 對話密鑰是對稱密鑰,對稱加解密速度很快。
- 服務器公鑰和私鑰是非對稱密鑰,非對稱加解密速度很慢。
- 使用非對稱加密生成可靠的對稱密鑰,使用對稱密鑰進行后續數據的加密。
- 上述帶序號報文可能一次發送,也可能分次連續發送。
- SSL和TLS可以當作一個東西。
- 服務器也可以不使用CA頒發的證書,而使用自己的證書。
優化
我們要對TCP和SSL/TLS握手耗時進行優化。有以下幾個因素:
- 數據往返延遲:主要受地理位置影響,使用較近的服務器進行數據傳輸會減少數據往返的時間,我們可以通過在不同的地區部署服務器(如:CDN,其也會用到DNS解析,可能在DNS解析階段就完成了對客戶端訪問域名到距離最近的服務器的映射),將數據放到接近客戶端的地方,可以減少網絡往返時間。
- 證書鏈:其實數據往返延遲優化不只是針對TCP握手階段的,后續基于TCP的數據傳輸都會收益,如SSL/TLS握手和后續的請求響應。那么證書鏈是影響SSL/TLS握手的一個重要因素,證書鏈是服務器向客戶端發送的證書內的信息,由站點證書、中間證書頒發機構的證書、根證書組成(比較類似DNS域名解析服務器之間的關系)。
- 原因:
- TCP慢啟動:由于TCP慢啟動(為避免擁塞,TCP連接初始只能發送較少的分組,然后等待客戶端確認,然后翻倍,經過幾次往返直至到達閾值)和TLS/SSL握手數據發送一般位于TCP連接慢啟動階段的關系,證書數據過多會超過TCP連接的初始值,會造成數據往返次數的成倍增加。
- 證書鏈驗證過長:由于客戶端瀏覽器在驗證證書可靠性時,會遞歸驗證鏈條中的每個節點至根證書,也會增加握手時間。
- 減少中間證書頒發機構的數量,優化至只有站點證書和一個中間證書頒發機構。
- 不要添加根證書信息,瀏覽器內置信任名單中有根證書。
- 握手次數:前兩點優化都是針對的握手時間的優化,握手次數也是影響延遲的重要因素。我們在后面談到大量請求的時候再說這一點。
- 初始擁塞窗口:適當增大初始擁塞窗口大小,即增大TCP連接初始可發送的分組大小。
3.獲得頁面響應
重定向響應
如果服務器返回了跳轉重定向(非緩存重定向),那么瀏覽器端就會向新的URL地址重新走一遍DNS解析和建立連接。
所以應該避免不必要的重定向。
頁面資源響應
在獲得了html響應之后,瀏覽器開始解析頁面,進入準備渲染的階段。下載優化同樣放在后面談到大量請求的時候再說這一點。
4.解析渲染頁面
我們需要將這個過程先分為兩個部分來看,頁面資源加載和渲染。
頁面資源加載
瀏覽器在解析頁面的過程中會去請求頁面中諸如js、css、img等外聯資源。
建立連接
同樣這些資源的加載也是需要建立TCP連接的,直接使用也需要進行DNS解析和握手。
優化
此處的請求次數與頻率相對于第一次請求頁面資源時要高很多,所以這里著重闡述下成批量的請求的優化。
瀏覽器目前使用的HTTP協議版本大多是1.1和2,二者有些不同,但是底層都是使用TCP進行數據傳輸。由于TCP握手耗時,和SSL/TLS更加耗時,我們需要減少整個加載過程中需要建立的連接的次數和耗時。
- 復用:針對HTTP1.1的最好方法是啟用長連接:HTTP 1.1提供了默認開啟長連接功能,相對于短連接(每請求一個資源建立然后斷開一次TCP連接),同一客戶端socket(瀏覽器可能會開多個端口并行請求)針對同一socket(域名+端口)后續請求都會復用一個TCP連接進行傳輸,直到關閉這個TCP連接。
- 加速:針對SSL/TLS握手有會話恢復機制,驗證通過后,可以直接使用之前的對話密鑰,減少握手往返。
加載之前
在服務器返回響應時,又存在幾種情況,如:服務器負載大,服務器宕機,無法及時或較快響應請求,服務器地理位置過遠或跨運營商導致延遲很高。
優化
這里跟建立連接部分的優化其實是公用的,但是單純的正常建立連接消耗資源較少,所以我們在這個再較完成的闡述一下。
- 增加帶寬:但是大部分情況下服務器帶寬并不是影響延遲的主要因素。
- 智能DNS解析:根據客戶端的IP地址,將域名解析為最近的或不跨運營商的服務器的IP地址,解決地理位置和跨運營商的延遲問題。
- CDN:使用某種分析方式根據節點服務器的地理位置、負載情況、資源匹配情況從遍布各地的節點服務器中找出最合適的靜態資源服務器。
- 負載均衡:使用DNS負載均衡、IP負載均衡、反向代理負載均衡等方式從一堆服務器(集群相同職責)或一組服務器(分布式職責區分)中選擇最合適的服務器處理請求。
- 這幾種技術可能是相互結合的,比如CDN會用到DNS智能解析和負載均衡等。
- 其中使用了跳轉重定向方式的會重新進行DNS解析和握手,其中一部分優化實際是在域名的DNS解析部分完成的。
開始加載
好了,服務器終于可以愉快的返回數據了。
HTTP 1.1
過程
- 雖然HTTP 1.1有長連接,一個TCP連接可以用來請求多個資源,但是這些資源的下載是串行的,比如使用這個TCP通道請求1.css、2.css、1.js,只有在前者傳輸成功完整完成后才會進行下一個的傳輸。
- 雖然瀏覽器一般會請求建立多個TCP連接(多個端口向服務器一個端口請求資源,新的TCP連接的建立會進行新的握手),去并行的請求頁面資源加快整體的下載速度,然而對每個域名的并行連接是有數量限制的(保護服務器負載,并受主機端口限制),所以我們還是要進行一些優化。
優化
- 減少
- 減少頁面中需要發起的請求總數,如我們常規使用的代碼合并,雪碧圖(精靈圖/Sprite合并小圖標),將圖片轉為base64寫入其他文件,避免空的img src屬性等。
- 切割拆分數據,讓首屏數據優先加載等。
- 增加:增加資源的分布域名,部署在不同域名下,“突破”瀏覽器并行連接限制(結合DNS部分,不易過于分散,且過多連接會共享帶寬,且移動端的解析更加緩慢)。
HTTP 2
由于HTTP 2提供了多路復用的功能,基于二進制數據幀和流的傳輸,使通過一個TCP連接進行數據分散、亂序、并行傳輸成為現實,即我們所有的資源都可以通過一個TCP連接不阻塞的并行傳輸。
所以我們針對HTTP 1.1的減少請求數量所做的合并優化、增加資源分布域名都成為了無效優化,可以丟掉。同時由于文件不用合并,進行文件更新時我們也不用再修改單個開發模塊更新所有(合并文件)模塊了。
加載中
總的來說是很簡單的過程,客戶端接收服務器傳輸返回的響應。
優化
傳輸的數據大小越小,那么傳輸就越快,延遲就越小。
- 更小
- Gzip:啟用Gzip可對響應體進行壓縮,可減少70%大小的數據體積。
- 減少cookie:去除不必要的cookie,設置合適過期時間。
- 舍棄cookie:對于靜態文件請求我們可以不要cookie,即HTTP1.1中提到的,分布在其他域名下,子域名設置合理的domian(cookie作用域)。
- 首部壓縮:HTTP2還提供了首部壓縮功能,即通過雙方共有的一些字典,將首部信息(狀態行、請求/響應頭)“映射”為更簡短的數據。
- 圖片:使用合適的圖片大小和圖片格式,可以節省大小。
- 緩存:小到最小的情況當然是不接受數據傳輸,使用本地緩存。一般使用服務器前一次返回的響應頭部字段進行控制。
- 強緩存:強緩存不會向服務器發送請求。
- Expires:http1.0字段,使用服務器時間做標識。
- Cache-Control:max-age=seconds,使用相對于請求的時間,不超過這個時長,直接使用緩存。還有其他的值。
- 協商緩存:
- Last-Modified/If-Modified-Since:資源最后修改時間,秒。瀏覽器客戶端發送If-Modified-Since字段,服務器響應Last-Modified字段。
- ETag/If-None-Match:資源的標識符,客戶端發送If-None-Match字段,服務器響應ETag字段,并比較兩者,決定返回緩存重定向還是其他,這個標識只比較內容,不關心資源時間。
- 合理拆分頁面資源,比如外聯js和css就可以獨立于html進行緩存。
關閉TCP
在資源下載完畢之后,需要關閉TCP連接。這段沒有什么可以優化的。
過程:
- TCP客戶端發送一個FIN=1(結束標志位)和隨機數seq=a,用來關閉客戶到服務器的數據傳送。
- 服務器收到這個關閉請求,返回ACK=1 ,ack=a + 1,此時服務器之前的數據可能還沒有傳輸完成。
- 數據傳輸完成后,服務器發送一個FIN=1和隨機數seq=b給客戶端。
- 客戶端返回ACK=1,ack=b + 1,并等待一段時間,確保服務器沒有返回沒收到確認報文的重傳申請,后關閉連接。
- 服務器收到確認報文后,驗證關閉連接。
總結
HTTP2 真好用。合理使用緩存。
頁面解析渲染
上述的資源加載是發生在頁面解析過程中的。那么瀏覽器的頁面解析渲染是怎么樣的一個過程呢?
過程
簡要來講就是:
- 處理HTML標記,構建DOM樹。
- 處理CSS標記,構建CSSOM樹。
- 將DOM樹和CSSOM樹融合成渲染樹(會忽略不需要渲染的dom)。
- 根據渲染樹來布局,計算每個節點的幾何信息。
- 在屏幕上繪制各個節點。
- 中間遇到各種資源時,會進行資源的下載。
問題
- 資源下載
- css下載時會阻塞渲染(帶有media屬性除外)。
- 遇到 script 標簽時,DOM構建停止,此時控制權移交至js,直到腳本(下載)執行完畢,此時瀏覽器有優化一般會下載其他資源,但是不會解析。如果js中有對CSSOM的操作,還會先確保CSSOM已經被下載并構建。
- 圖片資源下載不會產生阻塞。
- 重繪重排導致重新進行渲染樹的生成
- 重排(回流):會重新計算布局,通常由元素的結構、增刪、位置、尺寸變化引起,如:img下載成功后,替換填充頁面img元素,引起尺寸變化;也會由js的屬性值讀取引起,如讀取offset、scroll、cilent、getComputedStyle等信息。
- 重繪:簡單外觀的改變會引起重繪,如顏色變化等。
- 重排一定重繪。
優化
- dom
- 簡化dom結構,減少DOM樹和渲染樹構建成本,減少頁面元素個數,如使用列表表格數據分頁,簡單表格不要使用復雜第三方組件等方式。
- js
- 將js腳本標簽放在頁面body底部,減少對其他過程的阻塞。
- 延遲執行:對不修改頁面的外鏈script使用defer屬性,使腳本并行下載不阻塞,下載后不立刻執行,而在所有元素解析之后執行。
- 減少和合并不必要的dom相關操作,如使用DocumentFragment、修改classname而不是各項style等,減少對重繪和重排的觸發。
- css
- 將css放入head中,提前加載,并防止html渲染后重新結合css引起頁面閃爍。
- 減少css層級和css選擇器復雜度,提高解析速度,雖然瀏覽器有優化。
- 使用更高性能的css樣式,如flex代替float。
- 開啟復合層,如使用3d變換、opacity等,使該元素及其子元素不導致外部的重排,但是也有坑。
- 合理使用脫離文檔流的樣式,減少對外部重排的影響,如absolute。
- 文件數量
- 減少首次下載的文件數量大小,使用圖片懶加載,js的按需加載等方式,也可以節省用戶流量,甚至使用storage存儲進行js、css文件的緩存。
- 拆分頁面資源,首屏數據優先加載等。
5.其他優化措施
我們還可以采取一些和延遲、渲染無關的優化措施:
- 使用PWA,讓用戶在沒有得到數據時也能看到頁面。
- 對頁面某些ajax請求數據進行storage存儲。
- 加載進度、骨架圖、占位圖等類似讓用戶感覺好一點的措施。
- 及時更新升級服務器,優化措施依賴于服務器支持。
SS
CSS全稱為 Cascading Style Sheets,譯為層疊樣式表。CSS有三種樣式:內聯樣式,內嵌樣式,外聯樣式。
內聯樣式
內聯樣式通過HTML元素的style屬性來設置CSS樣式,語法如下:
style="CSS屬性:CSS屬性值";
示例代碼:
<!DOCTYPE html>
<html>
<head>
<title>內聯樣式</title>
<meta charset="UTF-8">
</head>
<body>
<!-- style="css屬性:css屬性值;" -->
<div style="color:red;">啟嘉班</div>
</body>
</html>
* 內聯樣式的缺點: 只對當前元素有效, 導致CSS代碼可能出現冗余
內嵌樣式表
通過HTML元素的style屬性來設置CSS樣式,語法如下:
<style>
選擇器{
屬性名:屬性值;
}
</style>
示例代碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>內嵌樣式</title>
<style>
p{
color: chartreuse;
font-size: 50px;
}
</style>
</head>
<body>
<p>19級啟嘉班</p>
</body>
</html>
優點: 相對于內聯樣式
*通過CSS選擇器定位HTML頁面中一個或一類元素
缺點:相對于內聯樣式
* 將CSS代碼嵌入到HTML頁面中,只能對當前HTML頁面有效
* 編碼的風格不符合編碼原則中的低耦合
* 導致CSS代碼可能出現冗余
外聯樣式
通過HTML元素的<link>元素來設置CSS樣式表
1.創建一個CSS文件,用來存儲CSS樣式內容
2.在HTML頁面中通過<link>元素引入外部指定的CSS文件
<link>元素:
* 作用: 用來引入HTML頁面外部的資源
屬性:
* rel屬性: 用來設置外部資源與當前HTML頁面的關系
* href屬性: 用來設置引入外部資源的路徑(相對路徑和絕對路徑)
語法結構如下:
<link rel="stylesheet" href="CSS文件路徑">
示例代碼:
<link rel="stylesheet" href="style.css">
優點: 相對于內嵌樣式表和內聯樣式
* 將CSS樣式與HTML頁面進行分離(低耦合)
* 不會出現內嵌樣式表或內聯樣式產生的代碼冗余
缺點: 可能對HTML頁面的加載造成負擔,導致性能下降
下一節更新CSS選擇器
益于智能手機的普及和各行各業互聯網+的運用,移動端的市場占比瘋狂增長。移動端+HTML5,這個組合對前端工程師來說是個不小的挑戰,如何讓開發的頁面能有更好的體驗?這就是我們今天討論的話題:移動端HTML5頁面前端性能優化。
如何優化HTML5在移動設置上的性能表現,首先需要明確以下幾個原則:
1、PC優化手段在Mobile端同樣適用。
2、在Mobile端我們提出三秒種渲染完成首屏指標。
3、基于第二點,首屏加載3秒完成或使用Loading。
4、Mobile端因手機配置原因,除加載外渲染速度也是優化重點。
5、基于第四點,要合理處理代碼減少渲染損耗。
6、基于第二、第四點,所有影響首屏加載和渲染的代碼應在處理邏輯中后置。
7、加載完成后用戶交互使用時也需注意性能。
HTML5優化具體可在以下幾個方面入手:
加載優化:合并CSS、JavaScript、小圖片,緩存一切可緩存的資源,使用長Cache和外聯式引用CSS、JavaScript,壓縮HTML、CSS、JavaScript,啟用GZip8使用首屏加載、按需加載、浪屏加載,通過Media Query加載,增加Loading進度條,減少Cookie,避免重定向,異步加載第三方資源。
圖片優化:使用智圖,盡可能用(CSS3、SVG,IconFont)代替圖片,使用Srcset,web圖優于JPG,PNG優于GIF,圖片不寬于640。
腳本優化:減少重繪和回流級存Dom選擇與計算,緩存列表length,盡量使用事件代理避免批量綁定事件,盡量使用ID選擇器使用touchstart、touchend代替click。
Css優化:CSS寫在頭部,JavaScript寫在尾部或異步;避免圖片和iFrame等空的Src;盡量避免重設圖片大小;正確使用Display的屬性;不濫用Float,不濫用Web字體,不聲明過多的Font-size,值為0時不需要任何單位;標準化各種測覽器前綴,避免讓選擇符看起來像正則表達式。
渲染優化:HTML使用Viewport,減少Dom節點,盡量使用CSS3動畫合理使requestAnimationFrame動畫代替setTimeout;適當使用Canvas動通畫;使用(CSS3 transitions,CSS3 3Dtransforms. Opacity. Canvas, WebGL.Video)來觸發GPU渲染。