所謂 File Prefetching 就是在一個頁面加載成功後,默默去預加載後續可能會被訪問到的頁面的資源。
前端資源預加載其實沒啥新鮮的,咱們倒騰這個事情的過程倒是頗有有意思也頗有啓發性。javascript
一、建一個獨立的頁面,裏面索引了各類須要預加載的css、js,代碼相似下面這樣。php
<html> <head> <link rel="stylesheet" href="//su.yzcdn.cn/v2/build_css/stylesheets/wap/showcase_d0fbaaef124a8691398704216ccd469a.css"> ...其餘須要預加載的css </head> <body> <script src="//su.yzcdn.cn/v2/build/wap/common_08b03c7826.js" onerror="_cdnFallback(this)"></script> ...其餘須要預加載的js </body> </html>
二、 在每一個頁面加入一個iframe(通常經過base模板統一加),這樣每一個頁面打開的時候都會加載上面這個頁面。假設上面的頁面的url是 https://xxx.com/common/prefetching.html
那麼咱們每一個頁面底部都有這麼一行代碼:css
<iframe src="https://youzan.com/common/prefetching.html" sytle="display:none;"></iframe>
要驗證某個file prefetching的方案是否真的有效,無非就是如下幾步: (假設A頁面使用了showcase_d0fbaaef124a8691398704216ccd469a.css
,而B頁面不會)html
這說明,這2個css文件是從cache裏讀的。若是Command/Ctrl+R來刷新頁面,咱們會看到這樣的結果: 前端
二者的差異是,Command/Ctrl+R的時候,瀏覽器會從cache裏找該靜態文件,若是找到了,會根據上次請求這個文件時獲得的cache-control
信息判斷該靜態文件是否已通過期了,若是沒有,會以 if-modified-since
、Etag
等信息做爲 request headers 向服務器請求這個文件,服務器若是認爲文件沒有變過,會返回Http code爲304,瀏覽器因而直接讀cache。具體不展開啦,能夠看 《HTTP caching 》 和 《Understanding HTTP/304 Responses》。java
_讓chrome終端打開的時候cache功能依舊有效_:Chrome終端的配置裏把Disable cache (while DevTools is open)
的勾選去掉
_清空全部cache_:地址欄裏輸入 chrome://settings/clearBrowserData
打開後勾上 Cached images and files
點 Clear browsing data
_查看瀏覽器當前cache的資源列表_:chrome://cache/
web
目前看來,上面這個 File Prefeching 的方案是有效的。不過這種是最簡陋的試驗版,存在幾個問題:chrome
因而,咱們上線使用的版本是這樣的:segmentfault
一、有一段每一個頁面都會被執行到的js:後端
// 打開一個iframe,下載以後頁面可能須要的js/css setTimeout(function() { var lastOpenTime = 0; var nowTime = (new Date()).getTime(); try { lastOpenTime = window.localStorage.getItem('staticIframeOpenTime'); } catch (e) {} if (lastOpenTime > 0 && (nowTime - lastOpenTime < 24 * 3600 * 1000)) { // 24小時打開一次iframe return; } var iframe = $('<iframe>').css('display', 'none'); iframe .attr('src', 'https://youzan.com/common/prefetching.html') .appendTo(document.body); try { window.localStorage.setItem('staticIframeOpenTime', nowTime); } catch (e) {} }, 3000); // 延時3秒鐘加載prefetching.html
二、prefetching.html 裏的資源想辦法讓他下載但不執行,基本上都是把這些css/js文件當作其餘類型的文件來加載,最後參照了《Preload CSS/JavaScript without execution》這篇文章,prefetching.html 中加載js文件的代碼大概是這樣的:
<script type="text/javascript"> window.onload = function () { var i = 0, max = 0, o = null, preload = [ '須要預加載的文件路徑' ], isIE = navigator.appName.indexOf('Microsoft') === 0; for (i = 0, max = preload.length; i < max; i += 1) { if (isIE) { new Image().src = preload[i]; continue; } // firefox不兼容 new Image().src 這種方式,因此除了IE都借用 object 來加載 o = document.createElement('object'); o.data = preload[i]; o.width = 0; o.height = 0; document.body.appendChild(o); } }; </script>
經過對預加載的js文件只下載不執行
、延時加載prefetching.html
、藉助localstorage的記錄一天只加載一次prefetching.html
,基本上解決了版本一的3個問題。
移動頁面全站上線後,平均loaded時間減小了0.15s,首屏時間沒有數據,不過收益應該是可觀的
不過,這個版本上線後,咱們發現頁面在prefetching的時候會假死,最後定位到是由於object加載js致使的(具體爲何會這樣還沒細究),考慮到咱們主要的頁面都是在手機端訪問的,基本上都是webkit內核(Image的方式在firefox中不兼容也不甚關係),因此咱們決定改用Image來加載全部JS。
這個版本除了解決第二個版本的假死問題,還加入了dns-prefetch,關於這部分的背景和思路能夠參考我另一篇文章:《預加載系列一:DNS Prefetching 的正確使用姿式》。
<!DOCTYPE html> <html> <head> <?php // dns prefething here ?> <link rel="dns-prefetch" href="//youzan.com/"> ... <?php // css prefething here ?> <link rel="stylesheet" href="//su.yzcdn.cn/v2/build_css/stylesheets/wap/showcase_d0fbaaef124a8691398704216ccd469a.css"> ... </head> <body> <?php // js prefething here ?> <script type="text/javascript"> (function(){ window.onload = function () { var i = 0, max = 0, preloadJs = [ 'js文件路徑', ... ]; for (i = 0, max = preloadJs.length; i < max; i += 1) { new Image().src = preloadJs[i]; } }; })(); </script> </body> </html>
上線後,絲絲潤滑無痛無癢,完美
注意哦,重點來咯!
儘早加載css是減小首屏時間的關鍵(引伸閱讀),直接把css inline到html裏是個不錯的方案。可是,這種方案的缺點是沒法充分利用瀏覽器緩存。因此,咱們嘗試在現有的File Prefetching 的基礎上,再進一步,讓首次訪問足夠快(用css line),後續訪問又能利用起瀏覽器緩存。
咱們對一部分重點頁面的css文件改用相似加載js的方式去加載,並在加載成功的回調里加一條cookie記錄標示該css文件已經被下載。這樣在後端輸出html的時候,能夠根據cookie的信息知道這幾個css文件是否是已經在瀏覽器裏cache住了。若是是則正常輸出一個<link>標籤。若是不是,說明用戶是第一次訪問這個頁面,則直接把css文件的內容inline到html裏以求最快出首屏。固然,也會出現從cookie上看客戶端已經cache了某個css文件,但實際上沒有的狀況,因爲這種狀況下html裏輸出的仍是一個link標籤,並不會影響正常的流程。
相關代碼大概是這樣的,須要的朋友能夠參考下:
var loadCss = function(key, url) { var image = new Image(); var date = new Date(); date.setTime(+date + 1 * 86400000); // 由於下載的不是圖片,實際觸發的是onerror事件 image.onload = image.onerror = function () { document.cookie = key + '=' + url.slice(url.indexOf('build_css')) + ';path=/;domain=.youzan.com;expires=' + date.toGMTString(); }; image.src = url; } preloadCss = { key1: '文件路徑', key2: '文件路徑2' ... } for (var key in preloadCss) { loadCss(key, preloadCss[key]); }
在作 File Prefetching 的過程中,每個版本的優化都是不一樣的人在作的:
A起了個頭 ->
B改進到能上線的標準 ->
發現有問題,C改進了它 ->
D又在這個基礎上作出了最後一個版本。
這種感受很是好:)
本文首發於有贊技術博客:http://tech.youzan.com/file-f...