昨天的一篇文章《經(jīng)典面試題-Web前端性能優(yōu)化方法之延遲加載》中,我們講到的是延遲加載技術,既然有延遲加載,那么就有預加載,今天這篇文章講述的是預加載如何實現(xiàn)Web性能優(yōu)化。
loading
預加載
預加載技術主要是通過預測瀏覽器在未來某個時間需要用到的資源,提前進行加載,在特定的時間段內(nèi)直接使用即可。作為開發(fā)者,肯定比普通用戶更加了解網(wǎng)頁資源的加載順序,因此通過預加載技術來告知瀏覽器Web中需要用到的核心資源,可以提升網(wǎng)頁的訪問速度。
預加載技術包括很多個方面,包括DNS prefetching,Preconnect,Prefetch,Subresource,Prerender等,我們一一來看
DNS prefetching
DNS prefetching顧名思義,就是提前解析DNS。
開發(fā)人員可以通過代碼告知瀏覽器盡早的解析特定DNS,當請求該域名下的文件時,就不用再解析DNS,而是可以直接使用該DNS下的文件了。這中方法在使用第三方庫時顯得尤為有效。
使用方法是,在<head>標簽中添加如下一段代碼
DNS預加載
Preconnect
Preconnect類似于DNS prefetching,它會預先建立TCP握手連接,在需要的時候還會建立TLS連接。
在現(xiàn)代瀏覽器中,會盡可能的預測網(wǎng)站所需要的連接,通過提前連接,可以消除在真實請求時的DNS解析,TCP連接的時間。
開發(fā)者可以通過代碼去告知瀏覽器哪些資源是可以提前連接的,代碼段如下
Preconnect提前連接
Prefetch
Prefetch是告知瀏覽器在未來某個時候需要用到的資源,可以提前請求并緩存下來,等到真正需要用的時候,可以直接從緩存中獲取。Prefetch可以預拉取圖片,css,js等資源文件。
通過以下代碼我們可以實現(xiàn)一張圖片的預拉取并緩存起來。
Prefetch預拉取
當然,瀏覽器并不是在任何情況下都會去執(zhí)行預拉取操作,比如在網(wǎng)絡情況不好的情況下不會請求較大的資源,F(xiàn)irefox只會在空閑時去執(zhí)行Prefetch操作。
Subresource
Subresource是用來指定加載資源的高優(yōu)先級,使用的方法如下。
Subresource提前加載
Subresource與Prefetch不同,Prefetch指定是未來的頁面需要用到的低優(yōu)先級的資源,而Subresource指定的是當前頁面需要用到的高優(yōu)先級的資源。因此,在指定當前頁面中需要用到的資源時,推薦用Subresource,而在后續(xù)頁面中用到的資源提前加載推薦用Prefetch。
Prerender
Prerender可以讓瀏覽器提前加載指定頁面上的所有資源。
Prerender我們可以這樣理解:瀏覽器實際上會在后臺開啟一個隱藏的標簽頁,在這個標簽頁中會下載頁面上需要的所有資源,如果用戶進入了指定的鏈接,則這個隱藏的頁面會立馬顯示出來,而用戶是無法辨別這個頁面是之前的隱藏頁面還是重新開啟的頁面。
Prerender預加載
并不是所有的頁面都適合Prerender特性,如果用戶最終未能進入指定的鏈接的頁面,那么提前加載的很多資源文件會浪費很多的帶寬。
我們可以分析一下一些比較適合使用Prerender特性的頁面:
一個具有分頁展示的圖片集或者文字集,在我們點擊下一頁的時候,可以將下一頁的內(nèi)容使用Prerender屬性預加載
注冊與登錄頁面,新用戶注冊完成之后一般會選擇去登錄,登錄頁面完成之后會跳轉到主頁面,這兩個過程就比較適合Prerender屬性的預加載
總結
今天這篇文章主要將的是預加載的Web性能優(yōu)化方式,后面會持續(xù)更新其他Web性能優(yōu)化的文章。
試經(jīng):懶加載和和預加載。難度:
什么是懶加載和預加載?請說下什么是懶加載和預加載?
懶加載和預加載其實都是一種頁面的性能優(yōu)化,都是為了提高網(wǎng)站和應用程序的性能,減少頁面的加載時間。
其中來說懶加載一般是指在頁面滾動時按需加載頁面上的圖片、視頻或者是其他資源。當頁面加載時只會加載頁面的初始內(nèi)容而不會加載頁面上的所有資源。所以當用戶去滾動頁面的時候再依據(jù)需要去加載對應的資源。這樣就可以減少頁面的加載時間,提高用戶體驗。
預加載是指在頁面加載時提前加載頁面上的資源,以便在用戶需要時可立即訪問。
那么預加載是通過頁面中的頭部標簽去使用外部<link>標簽,外鏈樣式以及<script>外部js方式去實現(xiàn)的。可以使用<link>標簽來預加載CSS文件,使用<script>標簽來預載圖片或者是其他資源。
這樣來說就可以避免在用戶需要的時候再去請求資源,提高響應速度和用戶體驗。
總體來說懶加載和預加載都是優(yōu)化網(wǎng)站的有效手段,所以我們基本上來說可以根據(jù)具體的場景和需要去選擇合適的方式。
一般來說懶加載需要去加載大量資源的頁面,就適用于需要去提高響應速度的關鍵資源。我是旭旭,助你面試成功!關注我,助你成功!
作為直接面向用戶的前端開發(fā)人員,我們都知道頁面首屏打開速度的重要性,直接關系到用戶的留存。
提升 H5 頁面打開速度的方法也有很多,有從網(wǎng)絡優(yōu)化入手的 CDN、gzip壓縮、Keep-Alive等,有從加載優(yōu)化入手的資源大小優(yōu)化、懶加載、按需加載、代碼拆分、Tree Shaking等,以及頁面渲染時的一些優(yōu)化。 在我們的項目中,上述優(yōu)化基本都有使用,當優(yōu)化達到一定程度,想要繼續(xù)僅基于 H5 自身優(yōu)化已難以再提升,就需要和客戶端合作,提升 H5 頁面的打開速度,比如離線緩存、預加載等。
這里介紹一個我們使用的接口預請求的方案,它可以相對低成本的提升 FMP (首次有效繪制) 的渲染速度,而且從我們的上線效果上來看提升非常明顯。
當我們打開在 APP 內(nèi)的 H5 頁面時,一般會經(jīng)過原生切頁面動畫、創(chuàng)建新頁面、加載 webview、加載 HTML、請求靜態(tài)資源并解析、獲取首屏數(shù)據(jù)、渲染內(nèi)容、下載圖片等步驟。 其實在加載的同時,可以利用 APP 在幫助 H5 獲取首屏數(shù)據(jù),這樣在 H5 頁面加載完成后,可以直接使用 APP 已請求的首屏數(shù)據(jù)進行渲染,這樣就節(jié)省了獲取首屏數(shù)據(jù)的時間。首屏數(shù)據(jù)接口越慢,該方案的價值就越大。
大體如下圖所示:
在做 H5 秒開優(yōu)化前,我們也討論過 SSR(服務端渲染) 的方案。 基于前后端分離的服務端渲染,一般由前端團隊使用 nodeJS 在服務器進行頁面渲染,前端服務器會組裝一個攜帶了具體數(shù)據(jù)的HTML文本,并且返回給瀏覽器。 服務端渲染雖然可以減少白屏時間,但相較于接口預請求有如下問題:
既然我們要利用 APP 來幫助 H5 獲取首屏數(shù)據(jù),而我們的 H5 頁面也不止一個,并且每個頁面的參數(shù)都不一樣。 那第一個要解決的問題就是怎么告訴 APP 在打開那些頁面時需要同步請求那些接口,這些接口的入?yún)⑹鞘裁礃拥模?/p>
當 APP 請求完成后,要解決的第二個問題是怎么把請求結果及時通知給 H5 頁面,并且防止 H5 頁面自身發(fā)起重復請求,增加服務端負擔?
第三個要解決的是如果 H5 修改了代碼,更新了參數(shù),而告知 APP 入?yún)⒌呐渲眠€未更新,怎么避免使用舊配置請求的結果?
我們先帶著問題看下整個方案的流程圖:
我們在每個 H5 項目先新增的另一個叫 app.config.json 的配置文件,配置了該頁面需要請求的主接口(可以是多個),請求方法、header、body、url 參數(shù)等。
單個頁面配置:
json復制代碼{
pageName: '頁面名稱',
key: 'H5頁面的URL', //頁面唯一路徑,作為key
preRequest: { // 接口預請求配置
appVersion: '8.0.1', // 支持app最低版本號,為了處理APP識別字段新增等變更情況
support: 0, // 是否支持接口預請求,0 | 1
apis:[ // 預請求接口信息,數(shù)組
{
fragment: "#/detail", // 新增fragment 指定對應的路由(hash模式)頁面
url: 'https://xxxx/api/address', // 請求url
method: 'get', // 請求類型,如get|post,不傳默認get
header: {}, // 請求頭,規(guī)則同請求體
body:{ // 請求體
shopId: "getUserInfo.shopId:String|''",
cityId:'getLocation.cityId:Int',
key5:'params.orderId',
key6:'params.xxx'
}
}
]
}
}
如上配置,頁面 URL、接口地址等都好理解。 在打開一個 H5 時,接口的入?yún)⒁话銇碜詢蓚€地方,最常見的是通過 URL 來獲取,另一種常見情況是通過 JSBridge 在 APP 內(nèi)獲取,比如全局的用戶信息等。 這里我們通過不同的字符串告知 APP 需要從哪里獲取那些參數(shù),比如 params.orderId 是告知 APP 需要從打開 H5 頁面的URL 中獲取 orderId 的參數(shù),而 getUserInfo.shopId,則是告知 APP 需要通過 JSBridge 中的 getUserInfo 方法獲取 shopId 參數(shù)。 這里APP并非真的去自己調(diào)用 JSBridge 的方法,而是告知 APP 該值需要和 JSBridge 中的方法達到同樣的效果。
也可以傳遞固定值,比如接口請求中入?yún)?type 是固定值 1,只需要和 APP 商議好規(guī)范,讀取識別即可。 以上我們解決了 APP 請求那些接口以及參數(shù)的問題。
我們再 H5 打包構建時對 app.config.json 進行處理,在增加些其他必備配置后,提交到 H5配置平臺,這時可以選擇在打開 APP 時調(diào)配置平臺的接口,但每次都讀取數(shù)據(jù)庫中的配置,會給服務端帶來極大的壓力。
這里我們做了一個小小的優(yōu)化,來很好的規(guī)避了這個問題。 我們再構建完成,發(fā)布上線后,在 H5配置平臺 打開預請求的開關時,由配置平臺讀取所有配置的頁面,生成一個配置的 JSON 文件,文件名類似 h5config.xxx.json,xxx 代表版本號,并把配置文件上傳到 CDN。 用戶打開 APP 時,通過接口下發(fā)最新配置文件的 地址和版本,APP 本地和緩存的文件對比,判斷是否要下載最新的配置文件。
這樣改造后,只需要在點擊發(fā)布的那一刻,讀庫生成新配置文件,大量的 C 端用戶訪問時,只需要調(diào)一個簡單的接口獲取最新配置版本和地址即可。
最終的配置文件類似這樣:
json復制代碼{
version: "0.0.1",
global:{
// 全局配置
},
pages: [
{
key: "https://xxx.com/pages/xxx.html",
pageName: "XXX活動頁",
openImageCache: 0, // 是否開啟圖片緩存,0關閉,1開啟
openPreRequest: 1, // 是否開啟接口預請求,0關閉,1開啟
config: {
// 上面的單個頁面配置生成而來
}
}
]
}
APP 每次打開 H5 頁面,都從配置中排查,是否有配置的頁面 URL,如果有并且打開了接口預請求,則在打開 H5 頁面的同時基于配置發(fā)起接口請求。
至此就解決了我們的第一個問題。
最初我們想到的是通過 JSBridge 去輪循獲取 APP 中請求的數(shù)據(jù),但這種方式顯然不夠優(yōu)雅。 后來通過 APP 在獲取到結果,并且 webview 觸發(fā)“onLoad ”事件后,把數(shù)據(jù)注入到 window 對象下。
如圖所示,正常情況下,存在兩種情況:
不管是哪種情況,我們都可以通過監(jiān)聽 注入的 window 對象,來獲取數(shù)據(jù),當?shù)谝淮螞]有讀取到時,會立即發(fā)起 H5 本身的請求,這樣即使接口預請求異常也不影響 H5 本身。 對于 window 對象我們通過 Object.defineProperty 進行監(jiān)聽,APP 和 H5 自身的請求誰先返回就用誰。
這樣就解決了第二個問題,既能提升又完全不影響 H5 自身業(yè)務。
上文我們提到,因為 H5 可以隨時發(fā)版,發(fā)版后,如果沒有及時更新配置文件,或者更新了但 APP 有緩存,沒有立即使用最新的配置文件。 此時如果 H5 本身的參數(shù)變化了,APP 還是按舊配置獲取請求,得到的數(shù)據(jù)并非新版本的數(shù)據(jù)時如何處理了?
還記得前面提到注入的 window.request_cache_xxx 對象嗎,其中 xxx 是配置中 url、method、header、body、params(params 是指參數(shù)掛載在 URL 上)這些字符串的 md5 值,每個H5 項目每次更新配置都有一個唯一的 md5 值。其中 H5 本地保留一份,另一份通過配置文件下發(fā)給APP,如果兩邊的 md5 值匹配不上,則 H5 不會使用 APP 注入的數(shù)據(jù)。
這里其實還有另一個風險。 如果 H5 開發(fā)者在代碼中的參數(shù)和配置文件不同步,比如迭代時間長后,忘記需要更新配置文件了。那么也就出現(xiàn)悲劇。 作為技術方案設計者,我們不能依賴人員的謹慎性,而應該靠系統(tǒng)設計來規(guī)避這些問題。
能發(fā)生的事情終將會發(fā)生
在這里我們通過修改 H5 項目的 HTTP 公共庫,使其在開發(fā)和測試環(huán)境都是通過讀取 app.config.json 中配置的接口、參數(shù)等來發(fā)起請求,這樣就做到開發(fā)調(diào)試和線上一致。
以上我們就解決了 APP 請求數(shù)據(jù)的正確性問題。
通過以上方案,我們基本上沒有借助其他部門的力量,單單依靠大前端組內(nèi)的資源,就極大解決了 H5 秒開的問題。 通過我們的性能監(jiān)控的數(shù)據(jù),在開啟 H5 秒開后包含接口預請求、DNS 預請求(通過 APP 對 H5 使用到的域名進行 DNS 緩存)策略后,所有 H5 項目,F(xiàn)MP 達到 1.2 秒的超過 80%。
PS:FMP 我們?nèi)〉氖菑挠脩粼?APP 點擊 H5 鏈接到主內(nèi)容渲染完成的時間
絕大部分頁面有 200~600ms 的提升,主要使用的 CMS 活動頁,安卓提升了 27%,iOS 提升了 43%,平均 FMP 在 900ms 左右。
視頻對比
作者:思考的Joey 鏈接:https://juejin.cn/post/7339846378125033523
*請認真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。