前端全鏈路優化

lvrice_start發表於2018-08-29

說到web前端效能優化,我想這絕對是一個大而複雜的工程,這涉及到web 前、後端的方方面面。做好前端的優化,不僅能給使用者有一個良好的使用者體驗,同時也為後端節省了大量的資源請求。絕對上有百利無一害的事情。 作為前端開發,該從哪下手去做好這件事情。首先我們要先弄明白。從前端發起請求到使用者看到介面,這其中到底經歷什麼??

1、DNS解析

DNS解析的過程就是尋找哪臺機器上有你需要資源的過程。當你在瀏覽器中輸入一個地址時,例如www.baidu.com,其實不是百度網站真正意義上的地址。網際網路上每一臺計算機的唯一標識是它的IP地址,但是IP地址並不方便記憶。使用者更喜歡用方便記憶的網址去尋找網際網路上的其它計算機,也就是上面提到的百度的網址。所以網際網路設計者需要在使用者的方便性與可用性方面做一個權衡,這個權衡就是一個網址到IP地址的轉換,這個過程就是DNS解析。它實際上充當了一個翻譯的角色,實現了網址到IP地址的轉換。網址到IP地址轉換的過程是如何進行的?

解析過程

DNS 解析是一個遞迴查詢的過程。 假如查詢www.baidu.com的IP地址過程。首先在本地域名伺服器中查詢IP地址,如果沒有找到的情況下,本地域名伺服器會向根域名伺服器傳送一個請求,如果根域名伺服器也不存在該域名時,本地域名會向com頂級域名伺服器傳送一個請求,依次類推下去。直到最後本地域名伺服器得到baidu的IP地址並把它快取到本地,供下次查詢使用。從上述過程中,可以看出網址的解析是一個從右向左的過程: com -> baidu.com -> www.baidu.com。但是你是否發現少了點什麼,根域名伺服器的解析過程呢?事實上,真正的網址是www.baidu.com.,並不是我多打了一個.,這個.對應的就是根域名伺服器,預設情況下所有的網址的最後一位都是.,既然是預設情況下,為了方便使用者,通常都會省略,瀏覽器在請求DNS的時候會自動加上,所有網址真正的解析過程為: . -> .com -> baidu.com. -> www.baidu.com.。

2、TCP 連線/釋放

連線

設主機B執行一個伺服器程式,它先發出一個被動開啟命令,告訴它的TCP要準備接收客戶程式的連續請求,然後服務程式就處於聽的狀態。不斷檢測是否有客戶程式發起連續請求,如有,作出響應。設客戶程式執行在主機A中,他先向自己的TCP發出主動開啟的命令,表明要向某個IP地址的某個埠建立運輸連線,過程如下: 1)主機A的TCP向主機B的TCP發出連線請求報文段,其首部中的同步位元SYN應置1,同時選擇一個序號x,表明在後面傳送資料時的第一個資料位元組的序號是x。 2)主機B的TCP收到連線請求報文段後,如同意,則發揮確認。在確認報文段中應將SYN置為1,確認號應為x+1,同時也為自己選擇一個序號y 3)主機A的TCP收到此報文段後,還要向B給出確認,其確認號為y+1 4)主機A的TCP通知上層應用程式,連線已經建立,當主機B的TCP收到主機A的確認後,也通知上層應用程式,連線建立。

釋放

在資料傳輸完畢之後,通訊雙方都可以發出釋放連線的請求。 1)資料傳輸結束後,主機A的應用程式先向其TCP發出釋放連線請求,不在傳送資料。TCP通知對方要釋放從A到B的連線,將發往主機B的TCP報文段首部的終止位元FIN置為1,序號u等於已傳送資料的最後一個位元組的序號加1。 2)主機B的TCP收到釋放連線通知後發出確認,其序號為u+1,同時通知應用程式,這樣A到B的連線就釋放了,連線處於半關閉狀態。主機B不在接受主機A發來的資料;但主機B還向A傳送資料,主機A若正確接收資料仍需要傳送確認。 3)在主機B向主機A的資料傳送結束後,其應用程式就通知TCP釋放連線。主機B發出的連線釋放報文段必須將終止位元置為1,並使其序號w等於前面已經傳送過的資料的最後一個位元組的序號加 1,還必須重複上次已傳送過的ACK=u+1。 4)主機A對主機B的連線釋放報文段發出確認,將ACK置為1,ACK=w+1, seq=u+1。這樣才把從B到A的反方向連線釋放掉,主機A的TCP再向其應用程式報告,整個連線已經全部釋放。

3、HTTP 請求

HTTP 請求它主要發生在客戶端。傳送HTTP請求的過程就是構建HTTP請求報文並通過TCP協議中傳送到伺服器指定埠(HTTP協議80/8080, HTTPS協議443)。 HTTP請求報文是由三部分組成: 請求行, 請求報頭和請求正文。

請求行

格式:Method Request-URL HTTP-Version CRLF eg: GET index.html HTTP/1.1

請求報頭

請求報頭允許客戶端向伺服器傳遞請求的附加資訊和客戶端自身的資訊。 PS: 客戶端不一定特指瀏覽器,有時候也可使用Linux下的CURL命令以及HTTP客戶端測試工具等。 常見的請求報頭有: Accept, Accept-Charset, Accept-Encoding, Accept-Language, Content-Type, Authorization, Cookie, User-Agent等。

請求正文

當使用POST, PUT等方法時,通常需要客戶端向伺服器傳遞資料。這些資料就儲存在請求正文中。在請求包頭中有一些與請求正文相關的資訊,例如: 現在的Web應用通常採用Rest架構,請求的資料格式一般為json。這時就需要設定Content-Type: application/json。

伺服器處理請求並返回HTTP報文

後端從在固定的埠接收到TCP報文開始,這一部分對應於程式語言中的socket。它會對TCP連線進行處理,對HTTP協議進行解析,並按照報文格式進一步封裝成HTTP Request物件,供上層使用。這一部分工作一般是由Web伺服器去進行。

狀態碼

狀態碼是由3位陣列成,第一個數字定義了響應的類別,且有五種可能取值:

  • 1xx:指示資訊–表示請求已接收,繼續處理。
  • 成功–表示請求已被成功接收、理解、接受。
  • 3xx:重定向–要完成請求必須進行更進一步的操作。
  • 4xx:客戶端錯誤–請求有語法錯誤或請求無法實現。
  • 伺服器端錯誤–伺服器未能實現合法的請求。
響應報頭

常見的響應報頭欄位有: Server, Connection Cache-Control Date Set-Cookie...。

響應報文

伺服器返回給瀏覽器的文字資訊,通常HTML, CSS, JS JSON, 圖片等檔案就放在這一部分。

4、瀏覽器解析渲染頁面

瀏覽器是一個邊解析邊渲染的過程。首先瀏覽器解析HTML檔案構建DOM樹,然後解析CSS檔案構建渲染樹,等到渲染樹構建完成後,瀏覽器開始佈局渲染樹並將其繪製到螢幕上。這個過程比較複雜,涉及到兩個概念: reflow(迴流)和repain(重繪)。DOM節點中的各個元素都是以盒模型的形式存在,這些都需要瀏覽器去計算其位置和大小等,這個過程稱為relow;當盒模型的位置,大小以及其他屬性,如顏色,字型,等確定下來之後,瀏覽器便開始繪製內容,這個過程稱為repain。頁面在首次載入時必然會經歷reflow和repain。reflow和repain過程是非常消耗效能的,尤其是在移動裝置上,它會破壞使用者體驗,有時會造成頁面卡頓。所以我們應該儘可能少的減少reflow和repain。 JS的解析是由瀏覽器中的JS解析引擎完成的。JS是單執行緒執行,也就是說,在同一個時間內只能做一件事,所有的任務都需要排隊,前一個任務結束,後一個任務才能開始。但是又存在某些任務比較耗時,如IO讀寫等,所以需要一種機制可以先執行排在後面的任務,這就是:同步任務(synchronous)和非同步任務(asynchronous)。JS的執行機制就可以看做是一個主執行緒加上一個任務佇列(task queue)。同步任務就是放在主執行緒上執行的任務,非同步任務是放在任務佇列中的任務。所有的同步任務在主執行緒上執行,形成一個執行棧;非同步任務有了執行結果就會在任務佇列中放置一個事件;指令碼執行時先依次執行執行棧,然後會從任務佇列裡提取事件,執行任務佇列中的任務,這個過程是不斷重複的,所以又叫做事件迴圈(Event loop)。

瀏覽器在解析過程中,如果遇到請求外部資源時,如影象,iconfont,JS等。瀏覽器會下載資源。請求過程是非同步的,並不會影響HTML文件進行載入,但是當文件載入過程中遇到JS檔案,HTML文件會掛起渲染過程,不僅要等到文件中JS檔案載入完畢還要等待解析執行完畢,才會繼續HTML的渲染過程。原因是因為JS有可能修改DOM結構,這就意味著JS執行完成前,後續所有資源的下載是沒有必要的,這就是JS阻塞後續資源下載的根本原因。CSS檔案的載入不影響JS檔案的載入,但是卻影響JS檔案的執行。JS程式碼執行前瀏覽器必須保證CSS檔案已經下載並載入完畢。

前面說完一個完整請求的過程,接下來說下每個環節可做優化。

優化

  • 設定DNS預解析,減少DNS 查詢的過程。
  • 減少http請求,合理設定 HTTP快取 -http協議是無狀態的應用層協議,意味著每次http請求都需要建立通訊鏈路、進行資料傳輸,而在伺服器端,每個http都需要啟動獨立的執行緒去處理。這些通訊和服務的開銷都很昂貴,減少http請求的數目可有效提高訪問效能。 減少http的主要手段是合併CSS、合併javascript、合併圖片。將瀏覽器一次訪問需要的javascript和CSS合併成一個檔案,這樣瀏覽器就只需要一次請求。很少變化的圖片資源可以直接通過 HTTP Header中的Expires設定一個很長的過期頭 ;變化不頻繁而又可能會變的資源可以使用 Last-Modifed來做請求驗證。儘可能的讓資源能夠在快取中待得更久。
  • 使用瀏覽器快取 對一個網站而言,CSS、javascript、logo、圖示這些靜態資原始檔更新的頻率都比較低,而這些檔案又幾乎是每次http請求都需要的,如果將這些檔案快取在瀏覽器中,可以極好的改善效能。通過設定http頭中的cache-control和expires的屬性,可設定瀏覽器快取,快取時間可以是數天,甚至是幾個月。
  • 使用伺服器壓縮 在伺服器端對檔案進行壓縮,在瀏覽器端對檔案解壓縮,可有效減少通訊傳輸的資料量。如果可以的話,儘可能的將外部的指令碼、樣式進行合併,多個合為一個。文字檔案的壓縮效率可達到80%以上,因此HTML、CSS、javascript檔案啟用GZip壓縮可達到較好的效果。但是壓縮對伺服器和瀏覽器產生一定的壓力,在通訊頻寬良好,而伺服器資源不足的情況下要權衡考慮。
  • 減少cookie傳輸 一方面,cookie包含在每次請求和響應中,太大的cookie會嚴重影響資料傳輸,因此哪些資料需要寫入cookie需要慎重考慮,儘量減少cookie中傳輸的資料量。另一方面,對於某些靜態資源的訪問,如CSS、script等,傳送cookie沒有意義,可以考慮靜態資源使用獨立域名訪問,避免請求靜態資源時傳送cookie,減少cookie傳輸次數。
  • 使用CDN 加速。
  • JS 程式碼程式碼優化 非同步載入js 指令碼(require.js)。預載入,延遲載入。減少重繪和迴流快取Dom選擇與計算,每次Dom選擇都要計算,快取他。 快取列表.length,每次.length都要計算,用一個變數儲存這個值。 儘量使用事件代理,避免批量繫結事件。減少作用域的找查詢,閉包的作用。

相關文章