前言
前端页面,对于用户来说,衡量其好坏,最直观的印象是加载速度。
如果页面一点开即渲染好了,那么用户第一印象分也会高,其次才是页面设计的美观度,页面布局的合理性等等因素。
一些常用的前端缓存
我想,前端工程师都知道,由于网络传输的环境的限制,很多时候,页面加载的瓶颈其实是在 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();
}
};
});
});
};
网友评论