所謂 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
cache-control
信息判斷該靜態文件是否已通過期了,若是沒有,會以 if-modified-since
、Etag
等信息做爲 request headers 向服務器請求這個文件,服務器若是認爲文件沒有變過,會返回Http code爲304,瀏覽器因而直接讀cache。具體不展開啦,能夠看 《HTTP caching 》 和 《Understanding HTTP/304 Responses》。讓chrome終端打開的時候cache功能依舊有效:Chrome終端的配置裏把Disable cache (while DevTools is open)
的勾選去掉
清空全部cache:地址欄裏輸入 chrome://settings/clearBrowserData
打開後勾上 Cached images and files
點 Clear browsing data
查看瀏覽器當前cache的資源列表:chrome://cache/
前端
目前看來,上面這個 File Prefeching 的方案是有效的。不過這種是最簡陋的試驗版,存在幾個問題:java
因而,咱們上線使用的版本是這樣的:web
一、有一段每一個頁面都會被執行到的js:chrome
// 打開一個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文件的代碼大概是這樣的:segmentfault
<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住了。若是是則正常輸出一個標籤。若是不是,說明用戶是第一次訪問這個頁面,則直接把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又在這個基礎上作出了最後一個版本。
這種感受很是好:)
本文首發於有贊技術博客:tech.youzan.com/file-frefet…