前端 ajax 加載緩存方案
前言
前端頁面,對於用戶來說,衡量其好壞,最直觀的印象是加載速度。
如果頁面一點開即渲染好了,那麼用戶第一印象分也會高,其次纔是頁面設計的美觀度,頁面佈局的合理性等等因素。
一些常用的前端緩存
我想,前端工程師都知道,由於網絡傳輸的環境的限制,很多時候,頁面加載的瓶頸其實是在 ajax 請求上面的。
對於 html、css、image 等等靜態資源,服務器一般都會通知瀏覽器緩存起來,用以提升頁面下次請求的加載速度。
比如我們衆所周知的 304 Not Modified 的應用場景。
即使服務器不做這種技術處理,而比較現代點的瀏覽器,一般也會對頁面的靜態資源作出緩存處理。
比如使用 chrome,你打開 devTools,多刷新幾次,便會發現,加載的時候,有很多靜態資源是從 cache 中拿出來的。
這麼處理,有一個核心的理念,就是我們常說的:用時間換空間!
Space–time tradeoff
如果有興趣的童鞋,可以瞭解下相關話題:https://en.wikipedia.org/wiki/Space%E2%80%93time_tradeoff
我們犧牲了電腦磁盤的空間甚至是內存,爲了減少了網絡傳輸的數據量,達到優化頁面加載的效果。
當然,這裏面需要一個動態的平衡,而如何掌握好這個平衡點,纔是重中之重,纔是區分編程水平高低的分水嶺。
ajax 數據持久化
但是對於 ajax 請求的數據,這些個機制,就無能爲力了。
特別,對於前端數據可視化的應用或者 3d 遊戲應用,一些複雜的對象,都是通過模型加載到場景中去的。
而模型數據,則一般都是通過 ajax 請求來加載的。
對於小型的模型,加載起來,每次通過 ajax 也無傷大雅,加載延遲也不太能感知的到。
但是有時候模型做的稍微精細一點的話,模型的文件數據也會開始膨脹起來了。
幾兆甚至幾十兆也常常是家常便飯。
模型太大,對顯卡的要求就會增高,一般差點的顯卡就會帶不動,這個問題是後話了,我們先略去不考慮,單就說模型的數據的加載。
如果每次都從服務器去加載模型數據,這個加載成本就偏高了。
那麼,我們可以利用 localStorage 或者 indexedDB 來做 ajax 數據的持久化工作。
localStorage
localStorage 優點是用起來簡單,方便,學習成本也低。
但是有個致命的缺點是,它單個頁面,允許存儲的容量太小了!
在 stackoverflow 上,有相關話題的討論,感興趣的同學可以自己去看看:https://stackoverflow.com/questions/2989284/what-is-the-max-size-of-localstorage-values。
如果想知道自己瀏覽器 localStorage 容量的限制,可以打開 devtools,粘貼入下面這段代碼,測試一下:
if (localStorage && !localStorage.getItem('size')) {
var i = 0;
try {
// Test up to 10 MB
for (i = 250; i <= 10000; i += 250) {
localStorage.setItem('test', new Array((i * 1024) + 1).join('a'));
}
} catch (e) {
localStorage.removeItem('test');
console.log('The max size of localStorage is:', (i - 250) / 1000, 'MB');
}
}
chrome 瀏覽器的 localStorage 大小限制爲 5 MB,這個大小,對於我們的 3d 模型資源來說,無異於杯水車薪。
那麼對於我們來說,最好的方式就是採用 indexedDB 來做 ajax 數據的持久化。
indexedDB
indexedDB,看名字我們就知道了,他是一種數據庫,一種瀏覽器提供給我們前端頁面直接使用的數據庫。
接下來,我就簡單的介紹一下,如何用 indexedDB 來做 ajax 數據持久化。
創建數據庫
定義一個 getDB 方法,獲取數據庫實例,方便我們進行增加、查詢等操作。
傳入 databaseVersion 來控制版本,如果升級了,刪除之前的數據庫中的表(ObjectStore)
var databaseVersion = 1;
var db;
var getDB = function() {
return new Promise(function(resolve, reject) {
function openDatabase() {
// 打開數據庫
var DBOpenRequest = indexedDB.open('test', databaseVersion);
DBOpenRequest.onsuccess = function(event) {
if (db === "opening") db = event.target.result;
resolve(db);
console.log('數據庫打開成功');
};
function createObjectStore(db, name){
if (db.objectStoreNames.contains(name)) {
db.deleteObjectStore(name)
}
db.createObjectStore(name, { autoIncrement: true });
}
// 數據庫首次創建版本,或者window.indexedDB.open傳遞的新版本(版本數值要比現在的高)
DBOpenRequest.onupgradeneeded = function(event) {
var db = event.target.result;
createObjectStore(db, 'modelPool');
resolve(db);
};
}
if (db === undefined) {
db = 'opening';
openDatabase();
} else {
if (db === 'opening') {
var timer = setInterval(() => {
if (db !== 'opening') {
resolve(db);
clearInterval(timer);
}
}, 50);
} else {
resolve(db);
}
}
});
};
增加數據
採用最簡單的,存儲 key value 的方式,將數據存儲到數據庫中的表中去
var add = function(value, key, storeName = 'modelPool') {
getDB().then(function(db) {
var transaction = db.transaction([storeName], 'readwrite');
var request = transaction.objectStore(storeName).add(value, key);
request.onsuccess = function(event) {
console.log('數據寫入成功');
};
request.onerror = function(event) {
console.log('數據寫入失敗');
};
});
};
查詢數據
var read = function(url, storeName = 'modelPool') {
return getDB().then(function(db) {
var transaction = db.transaction([storeName]);
var objectStore = transaction.objectStore(storeName);
return new Promise(function(resolve, reject) {
var request = objectStore.get(url);
request.onerror = function(event) {
console.log('事務失敗');
reject();
};
request.onsuccess = function(event) {
if (request.result) {
resolve(request.result);
} else {
console.log('未獲得數據記錄');
reject();
}
};
});
});
};