正確的資源下載/執行優先級,並減小頁面加載期間瀏覽器的空閒時間,是提高Web應用性能的最重要手段之一。在實際Web應用中,此優化方案被證實比減小代碼大小更爲直接有效,此類型的優化對產品開發節奏的影響比較小,它只須要少許的代碼更改和重構。
Javascript
,XHR
,圖片預加載讓瀏覽器在關鍵頁面預加載動態資源:動態加載的JavaScript、預加載的XHR-GraphQL數據請求。javascript
<link rel="preload" href="index.js" as="script" type="text/javascript">
動態加載JavaScript
,一般是指經過import('...')
爲指定客戶端由路由加載的腳本。在服務端接收到請求時,能夠知道這個特定的服務端入口文件將須要哪些客戶端路由的動態腳本,而且在頁面初始化渲染的HTML中,爲這些腳本添加預加載邏輯。java
在某個頁面入口文件中,必然會執行1個特定的GraphQL請求,能夠預加載這個XHR請求,這個點很是重要,由於在某些場景下GraphQL請求會消耗大量時間,頁面必需要等到這些數據加載好才能開始渲染。json
<link rel="preload" href="/graphql/query?id=12345" as="fetch" type="application/json">
除了更早地開始資源加載,預加載還有額外的好處:提高異步腳本加載的網絡優先級,對於重要的[異步腳本]來講,這點很是重要,由於它們的網絡優先級默認是low
。這意味着它們的優先級和屏幕以外的圖片同樣(low
),而頁面的XHR
請求和屏幕內的圖片網絡優先級則比它們要高(high
)。這致使頁面渲染所需的重要腳本的加載可能被阻塞,或和其餘請求共享帶寬。api
預加載的問題:它提供的額外控制會帶來額外的責任,即設置正確的資源優先級。當在低速移動網絡區域、慢WIFI網絡或丟包率比較高的場景中測試時,<link rel="preload" as="script">
的網絡請求優先級會比<script />
標籤的JavaScript
腳本高,而<script />
標籤的腳本纔是頁面渲染首先須要的,這將增長整個頁面的加載時間。數組
JavaScript
包經過客戶端路由當前頁面須要異步加載的。瀏覽器
JavaScript
資源。JavaScript
資源加載的順序。構建一個優先任務的抽象來處理異步加載的隊列,這個預加載任務在初始化時優先級是idle
(利用requestIdleCallback
函數,window.requestIdleCallback()
方法將在瀏覽器的空閒時段內調用的函數排隊。在主事件循環上執行後臺和低優先級工做,而不會影響延遲關鍵事件,如動畫和輸入響應。),因此它會到瀏覽器不執行任何其餘的重要任務時纔開始。提高優先級的方法是經過取消全部待執行的空閒任務,這樣預加載任務就能當即執行。緩存
early flush
(提早刷新)和progressive HTML
(漸進式HTML
)來推送數據如何讓瀏覽器尚未任何服務端的HTML
返回就發起請求?解決方案是服務器主動向瀏覽器推送資源,這有點像利用http/2
的push
特性,它具備很是好的瀏覽器兼容性,而且不須要爲實現此特性而增長服務端基礎架構的複雜性。性能優化
它的主要實現包含兩點:服務器
HTTP
分塊傳輸編碼HTML
Chunked transfer encoding(分塊傳輸編碼)是HTTP/1.1
協議中的一部分,從本質上來看,它容許服務端將HTTP
的返回切碎成多個chunk
(塊),而後以分流的形式傳輸給瀏覽器。瀏覽器不斷接收這些塊,在最後一個塊到達後將它們聚合在一塊兒。網絡
它容許服務器在完成每一個chunk
時,就將此時的HTML
頁面的內容流式傳輸到瀏覽器,沒必要等待整個HTML
完成。服務器一收到請求,就能夠將HTML
的頭部flush
給瀏覽器(early flush),減小了處理HTML
剩餘內容的時間。對於瀏覽器來講,瀏覽器在收到HTML
的頭部時,就開始預加載靜態資源和動態數據,此時服務器還在忙於剩餘HTML
內容的生成。
利用[分塊傳輸編碼]在傳輸完成的同時講其它數據推送到客戶端。對於服務端渲染的Web應用,通常採用HTML
格式返回;對於SPA
,能夠用JSON
格式數據推送到瀏覽器。
JSON
緩存來存儲服務端返回的數據。// 服務端將會寫下全部它已經在準備的請求路徑 // 這樣客戶端就知道等待服務端返回數據便可,不須要本身發送XHR請求 window.__data = { '/my/api/path': { // 客戶端發起請求後的回調都存在waiting數組中 waiting: [] } }; window.__dataLoaded = function (path, data) { const cacheEntry = window.__data[path]; if (cacheEntry) { cacheEntry.data = data; for (let i = 0; i < cacheEntry.waiting.length; i++) { cacheEntry.waiting[i].resolve(cacheEntry.data); } cacheEntry.waiting = []; } };
在把HTML
刷新到瀏覽器後,服務端就能夠本身執行API
請求的查詢,完成後將JSON
數據以[包含script
標籤的HTML
片斷]的形式刷新到頁面中。當這個HTML
片斷被瀏覽器接收並解析後,它會被數據寫入JSON
緩存對象中。這裏有個關鍵技術點:瀏覽器會在接收到chunks
的時候就當即開始渲染。因此,能夠再服務端並行生成一系列API
數據,並在每一個API
數據準備好時,就當即將其刷新到JS塊中。
當客戶端JS準備好請求某個特定數據的時候,它將先檢查JSON緩存對象中有沒有數據,而不是發起一個XHR請求。若是JSON緩存對象中已經有數據,它將當即獲得返回;若是JSON緩存對象已標記pending
,它將把請求的resolve
回調註冊到對應的waiting
數組中,請求完成後,執行對應的resolve
回調。
function queryAPI(path) { const cacheEntry = window.__data[path]; if (!cacheEntry) { // 沒有緩存對象,直接發起一個普通的XHR請求 return fetch(path); } else if (cacheEntry.data) { // 服務端已經推送好數據 return Promise.resolve(cacheEntry.data); } else { // 服務端正在生成數據或推送中 // 把請求成功的resolve回調放到cacheEntry.waiting隊列中 // 當接收到數據後,回調會按順序執行 const waiting = {}; cacheEntry.waiting.push(waiting); return new Promise((resolve) => { waiting.resolve = resolve; }); } }
此項優化的效果很是明顯:桌面端用戶訪問頁面的渲染完成的時間減小14%,移動端用戶(有更高的網絡延遲)更是減小了23%。
如何讓頁面更快地獲取到數據?惟一的思路就是不經由網絡的請求和推送數據。能夠採用緩存優先的渲染機制來實現