更多前端文章:http://lvtraveler.github.io/
关于存储
说到存储,你可能会想到这是服务器端的一种设置。
服务器端的存储介质大体上分为4种:
- cache:缓存,它可以让从数据库、磁盘上输出的东西/数据放置在缓存里,从而减少数据库或是磁盘的读取与写入(IO)操作;
- 磁盘文件:如,我们常常会将图片、视频等文件存放在磁盘上;
- 数据库:mySql\mongoDB…关系\非关系数据库;
- 内存:通常放置频繁要使用到的东西,能够提高读取效率;缓存(cache)也是存放在内存里的;
HTML的存储-cookies
在HTML5出生之前,通常在浏览器(客户端)使用cookies来存储客户端的内容;
cookies的特点:
- 每次的http请求头中,都会带有cookies——缺点;
- 每个域名只能存储4K大小的cookies;
- 主域名污染:如果我们使用cookies存储主域名的东西,那么子域名下得Http请求都会带上主域名的东西;
如果关联上网络,那么将带来安全问题。
所以,通常我们会使用cookies用在如购物车、身份验证等问题上。
下面,我们来看一下百度首页的cookies在浏览器端的一个存储形态:
如图:
HTTP这一列,如果在setCookie的时候,这里就会打钩,这与HTTPOnly相关。
HTTPOnly:
如果把HTTPOnly设置为true,那么cookies只能被server服务器端来读取或是修改,客户端没有权限进行读取和修改。例如,我们在进行身份验证的时候,就可以使用这个。
Secure:与安全相关,如果设置了,那么请求只能是来自HTTP加密请求。
HTML的存储-UserData
- 只有IE支持,有微软提供API,但不符合W3C标准;
- 存储在XML文件中;
HTML5的存储
针对以上问题,HTML5的出现,需要解决以下问题:
- 解决4K的大小问题;
- 解决请求头常带存储信息的问题;
- 解决关系型存储的问题;
- 跨浏览器平台问题;
HTML5存储形式
- 本地存储——localstorage \sessionstorage
- 离线缓存——application cache
- IndexedDB、Web SQL
本地存储
API:
localstorage 、sessionstorage
存储形式:
key–>value
过期时间:
localstorage:永久存储,永不失效,除非手动删除
sessionstorage:重新打开页面,或是关闭浏览器,sessionstorage才会消失;
存储大小:
每个域名能存5M;
支持情况:
IE8+,safari3.2+,chrome,firefox等主流浏览器都支持;
使用方法——localstorage\sessionstorage
主要涉及到5个方法:
- getItem:获取localstorage\sessionstorage
- setItem:设置localstorage\sessionstorage
- removeItem:移除localstorage\sessionstorage
- key:获取某一个位置上的key值,按值从0开始索引;
- clear:全部清除localstorage\sessionstorage
例如:我们打开www.baidu.com
在控制台Console输出面板,输入:
localStorage.setItem("test1","test");
那么在Resources面板的Local Storage下,将出现Key=test1,value=test的记录
localStorage.getItem("test1");
//输出test
localStorage.key(0);
//输出BDSUGSTORED
sessionstorage的API与localstorage一样,但是你要注意一点:
sessionStorage需要在浏览器关闭或是重新打开页面,才会消失;
本地存储可以存储什么?
- 数组(需要将其序列化为字符串才能存储);
- json数据——将其转化为字符串存储;
- 图片
- 脚本、样式文件:通过ajax
只要能被转化为字符串的数据,都能被localstorage存储;
本地存储如何存储图片
先来看一段代码:
var src="demo.jpg";
function set(key){
var img=document.createElement(‘img‘);
img.addEventListener("load",function(){
//创建一个canvas
var imgCanvas=document.createElement("canvas"),
imgContext=imgCanvas.getContext("2d");
//确保canvas元素的大小和图片的尺寸一致
imgCanvas.width=this.width;
imgCanvas.height=this.height;
//渲染图片到canvas中,使用canvas的drawImage()方法
imgContext.drawImage(this,0,0,this.width,this.height);
//用canvas的dataUrl的形式取出图片,imgAsDataURL是一个base64的字符串
var imgAsDataURL=imgCanvas.toDataURL("image/png");
//保存到本地存储中
//使用try-catch()查看是否支持localstorage
try{
localStorage.setItem(key,imgAsDataURL);//将取出的图片存放到localStorage
}
catch(e) {
console.log("Storage failed:"+e);//存储失败
}
},false);
img.src=src;
}
function get(key) {//从本地缓存获取图片并且渲染
var srcStr=localStorage.getItem(key);//从localStorage中取出图片
var imgObj=document.createElement(‘img‘);//创建一个img标签
imgObj.src=srcStr;
document.body.appendChild(imgObj);
}
注释:
(1)、这个比较适合用在不常更改的图片,但是如果图片的base64大小比较大的话,将比较耗费localStorage的资源;
(2)、canvas有一个安全策略的问题:如果图片和你本身请求的域名不在同一个域名下,浏览器会报出一个安全问题,这个时候我们要给我们的服务器加一个“允许跨域”访问的响应头————Access Orign=*,这样来保证你的图片可进行跨域被canvas来画;
HTML5本地存储需要注意的:
- 使用前判断浏览器是否支持localStorage;(IOS浏览器在无痕模式浏览下,是无法打开localStorage;以及,其他奇葩浏览器,在存储localstorage的时候报错)
做法:根据前面代码,我们在检查是否支持,先进行setItem()一次,然后对setItem进行异常捕获;
- 写数据的时候,需要异常处理,避免超出容量抛出错误;
localStorage本身只有5M;
- 避免把敏感的信息存入localStorage;
- key的唯一性;重复写,将会覆盖之前的key;
HTML5本地存储使用限制:
- 存储更新策略,过期控制:localStorage是永不过期的,业务上如果想实现一些过期策略,需要在localStorage上加一层处理过期的机制;
- 各个子域名之间不能共享存储数据;(借助H5的postMessage()这个API做一些跨域上得处理)
- 超出存储大小之后如何存储——使用一些如LRU、FIFO的算法去淘汰一些旧的数据;
- server端如何取到数据——使用post/get参数
处理过期控制
先来看一下代码:
function set(key,y){
var curTime=new Date().getTime();
//存储一个当时存储时候的时间
localStorage.setItem(key,JSON.stringify({data:v,time:curTime}));
}
function get(key,exp) {
var data=localStorage.getItem(key);
var dataObj=JSON.parse(data);
if(new Date().getTime()-dataObj.time>exp) {//get出来的时间减去当时存储的时间大于过期时间,那么就认为过期
console.log("过期");
}else {
//否则,返回值
console.log("data="+dataObj.data);
}
}
本地存储使用场景
- 本地数据存储,减少网络传输
- 在弱网络的环境下,会发生高延迟,低带宽,应该尽量把数据(如脚本、样式)本地化;
我们来看一张图,显示的是本地存储和网络拉取耗时的对比:
IndexedDB
概念
IndexedDB,是一种能做浏览器中持久地存储结构化数据的数据库,并且为web应用提供了丰富的查询能力;
支持情况
chrome11+\opera不支持\firefox 4+\IE 10+,移动端浏览器支持能力弱
存储结构
IndexedDB是按域名分配独立空间,一个独立域名下可以创建多个数据库,每个数据库可以创建对个对象存储空间(表/table),一个对象存储空间可以存储多个对象数据;
如图:
使用IndexedDB实现离线数据库
这里我们主要从IndexedDB 的四大功能入手:
- 增删改
- 事务处理
- 游标
- 索引
下面我们通过一段代码来讲解,请关注里面的注释:
<!DOCTYPE html>
<html>
<div class="form-group">
<label for="name">姓名:</label><input type="text" id="name" value="" />
<label for="phone">电话:</label><input type="text" id="phone" value="" />
<label for="address">地址:</label><input type="text" id="address" value="" />
<input type="button" id="seletBtn" value="查询" />
<input type="button" id="add" value="添加" />
<input type="button" id="deleteDB" value="删除数据库" />
</div>
<script type="text/javascript">
var db;
var arrayKey=[];
var openRequest;
var lastCursor;
var indexedDB=window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;//indexedDB在不同的浏览器下不同
var dbName="person";//数据库名称
var tableName="testTable";//表名称
function init() {
openRequest=indexedDB.open(dbName);//页面加载时先打开一个DB,如果该DB存在,则打开;不存在,则新建
//触发事件——当一个“新的数据库”被创建或者数据库的“版本号”被更改时触发
openRequest.onupgradeneeded=function(e){
console.log("onupgradeneeded");
var thisDb=e.target.result;
console.log(thisDb.version);
//检查这个数据库中是否包含我们要查找的表
if(!thisDb.objectStoreNames.contains(tableName)){
//不包含——创建一个表
console.log("需要创建一个objectStore");
//keyPath:主键,autoIncrement:主键自增
var objectStore=thisDb.createObjectStore(tableName,{keyPath:"id",autoIncrement:true});
//创建表的时候,指定哪些字段是能被索引的
objectStore.createIndex("name","name",{unique:false});//创建索引
objectStore.createIndex("phone","phone",{unique:false});
}
}
//触发事件——成功打开一个数据库时触发
openRequest.onsuccess=function(e){
db=e.target.result;
console.log(db.version);
db.onerror=function(event){
alert("数据库错误:"+event.target.errorCode);
console.dir(event.target);
};
//判断该数据库中有没有这个表
if(db.objectStoreNames.contains(tableName)){
//存在这个表
console.log("包含表:"+tableName);
//通过事物机制操作一个表的读写,从而保证数据的一致性和可靠性
var transaction=db.transaction([tableName],"readwrite");
//事物的事件
transaction.oncomplete=function(event){
console.log("完成");
};
transaction.onerror=function(event){
console.dir(event);
};
var objectStore=transaction.objectStore(tableName);//通过事物获取表中一个objectStore对象,即表的对象
//遍历表的记录——游标-openCursor,这是indexedDb的重点
objectStore.openCursor().onsuccess=function(event){
var cursor=event.target.result;
if(cursor){
console.log(cursor.key);
console.dir(cursor.value);
render({key:cursor.key,name:cursor.value["name"],phone:cursor.value["phone"],address:cursor.value["address"]});
lastCursor=cursor.key;//如果不设置lastCursor,那么游标默认是下一条接着下一条来遍历;设置了lastCursor,游标将循环遍历
cursor.continue();
}else {
console.log("请使用游标来搞定");
}
};
objectStore.openCursor().onerror=function(event){
console.dir(event);
};
}
}
//添加新记录
document.querySelector("#add").addEventListener("click",function(){
var name=document.querySelector("#name").value();
var phone=document.querySelector("#phone").value();
var address=document.querySelector("address").value();
var person={"name":name,"phone":phone,"address":address};//设置对象
//通过事务——操作表
var transaction=db.transaction([tableName],"readwrite");
transaction.oncomplete=function(event){
console.log("事务处理完成");
};
transaction.onerror=function(event){
console.dir(event);
};
var objectStore=transaction.objectStore(tableName);//创建一个表对象
objectStore.add(person);//添加对象到表中——add()
//将新增的记录显示处理
objectStore.openCursor().onsuccess=function(event){
cursor=event.target.result;
var key;
if(lastCursor==null){
key=cursor.key;
lastCursor=key;
}else {
key=++lastCursor;
}
render({key:key,name:name,phone:phone,address:address});
console.log("成功添加新记录:"+key);
console.dir(person);
}
});
//删除指定ID
function deleteRecord(id){
var transaction=db.transaction([tableName],"readwrite");
transaction.oncomplete=function(event){
console.log("事务处理完成");
};
transaction.onerror=function(event){
console.dir(event);
};
var objectStore=transaction.objectStore(tableName);
var removeKey=parseInt(id);
var getRequest=objectStore.get(removeKey);//获取索引值---get()
getRequest.onsuccess=function(e){
var result=getRequest.result;
console.dir(result);
}
var request=objectStore.delete(removeKey);//删除——delete
request.onsuccess=function(e){
console.log("删除成功");
};
request.onerror=function(e){
console.log("删除错误"+e);
};
//隐藏删除的DOM
document.getElementById(removeKey).style.display="none";
}
//查询记录
document.querySelector("#seletBtn").addEventListener("click",function(){
var curName=document.getElementById("selname").value;
var transaction=db.transaction([tableName],"readwrite");
transaction.oncomplete=function(event){
console.log("事务处理完成");
};
transaction.onerror=function(event){
console.dir(event);
};
var objectStore=transaction.objectStore(tableName);
var boundKeyRange=IDBKeyRange.only(curName);//生成一个表示范围的Range对象---IDBKeyRange,有4个方法,only\lowerBound\upperBound\bound
objectStore.index("name").openCursor(boundKeyRange).onsuccess=function(event){
//从indexedDb中找到name
var cursor=event.target.result;
if(!cursor){
return;
}
var rowData=cursor.value;
console.log(rowData);
document.getElementById(‘content‘).innerHTML="";
render({key:cursor.value.id,name:cursor.value["name"],phone:cursor.value["phone"],address:cursor.value["address"]});
cursor.continue();
};
});
//删除数据库
document.querySelector("#deleteDB").addEventListener("click",function(){
//使用deleteDatabase()
var deleteDB=indexedDB.deleteDatabase(dbName);
var content=document.querySelector("#content");
while(content.firstChild){
content.removeChild(content.firstChild);
}
deleteDB.onsuccess=function(event){
console.log("删除成功");
};
deleteDB.onerror=function(event){
console.dir(event.target);
};
});
//渲染
function render(opt){
var child_node = document.createElement("div");
var child_node_child1 = document.createElement("div");
var child_node_child2 = document.createElement("div");
var child_node_child3 = document.createElement("div");
var child_node_child4 = document.createElement("div");
child_node_child1.setAttribute("class","table_child");
child_node_child2.setAttribute("class","table_child");
child_node_child3.setAttribute("class","table_child");
child_node_child4.setAttribute("class","table_child");
child_node_child1.setAttribute("style","float:left");
child_node_child2.setAttribute("style","float:left");
child_node_child3.setAttribute("style","float:left");
child_node_child4.setAttribute("style","float:left");
child_node_child1.innerHTML = name;
child_node_child2.innerHTML = opt.phone;
child_node_child3.innerHTML = opt.address;
child_node_child4.innerHTML = "<input type=‘button‘ value=‘删除‘>"
child_node.appendChild(child_node_child1);
child_node.appendChild(child_node_child2);
child_node.appendChild(child_node_child3);
child_node.appendChild(child_node_child4);
child_node.setAttribute("class","table_tr");
child_node.setAttribute("id",opt.key);
var content = document.getElementById(‘content‘);
content.appendChild(child_node)
}
}
</script>
</body>
</html>
离线缓存——application Cache
何为离线缓存
它是能让web应用在离线的情况下继续使用,通过一个叫manifest的文件指明需要缓存的资源;你可以通过
navigator.online
检测是否在线;
原理
如图:
解释:
(1)用户通过浏览器(browser)去访问应用,首先检测浏览器是否有一个叫做“App cache”的东西存在,如果存在,则从中检索出app cache所要缓存的list,然后把资源(缓存在浏览器中)拉取出来,返回给用户;
(2)在访问的同时,会检查server上一个叫做manifest的文件,如果该文件有更新,就把manifest指定的文件从server端重新拉取一次,然后把这些缓存在浏览器中,并更新相应的app cache文件;如果manifest这个文件没有更新,那么就啥也不做。
从上图,我们总结2点:
- 缓存机制的改变,会更新app cache.但是,用户访问,会返回上一次的结果。这样一来,会有一个麻烦,即如果你的业务发生更改,你就需要去更新一次manifest。
注意:更改完,第一次是不生效的,只有第二次刷新才会生效;
- 如果有一个文件要更新,你就要去更新manifest,而更新manifest文件,它会把server上的文件全部重新拉取一次,而非只是拉取你需要更改的那个文件,这就会造成损耗;
浏览器支持情况
safari on ios 3.2+\android 1,5+\window phone 9+
应用
例子:cache.appcache
CACHE MANIFEST
#version 1.0
CACHE:
#需要缓存的文件
/css/a.css
/js/a/js
/images/a.png
NETWORK:
#每次重新拉取的文件
*
FALLBACK
#离线状况下代替的文件
/404.html
在页面上引入manifest文件:
<html manifest="cache.appcache">
在服务器添加mime-type text/cache-manifest
如果在服务器上添加:
找到你的xampp/apache/conf目录,找到mime.types文件,在最后面添加一条记录:
text/cache-manifest appcache
(appcache是后缀名,你可以选择其他的)
我们来看一个例子:
<html lang="en" manifest="cache.appcache">
<head>
<meat charset="utf-8" />
</head>
<body>
<h1><demo1/h1>
<script type="text/javascript">
window.addEventListener(‘load‘,function(e){
//监听app cache的updateready事件
window.applicationCache.addEventListener(‘updateready‘,function(e)){
console.log(window.applicationCache.status);
if(window.applicatioinCache.status==window.applicationCache.UPDATEREADY){
//application cache的版本号发生改年
window.applicationCache.swapCache();
window.location.reload();
}else {
console.log("manifest没有更改");
}
},false);
},false);
</script>
</body>
</html>
注意:
- app cache会自动地将本页当做一个静态页缓存;
- 如果你要更新,请更新server端的manifest文件的版本;
- 如果你不想启用app cache,或者说现在app cache不适合你现在的应用,那么有一个做法:
更改server端上manifest文件的名称,例如cache1.appcache
,这个时候再去刷新浏览器,首先,浏览器还是会从app cache缓存中读取缓存,到第二次刷新的时候,浏览器会到server端查找manifest文件,发现这个文件不存在,那么浏览器会走网络从Server上重新拉取文件;
app cache优势:
- 完全离线
- 资源缓存,加载更快
- 降低服务器负载
app cache缺陷:
- 含有manifest属性的当前请求页无论如何都会被缓存;
- 更新需要建立在manifest文件的更新,文件更新后是需要页面再次刷新的,并且在第2次刷新才能获取新资源;
- 更新是全局性的,无法单独更新某个文件;
- 对于链接的参数变化的敏感的,任何一个参数的修改都会被重新缓存,例如:index.html和index.html?v=1会被认为是不同文件,分别缓存;
app cache适用场景
- 单地址页面
- 对实时性要求不要的业务
- 离线web应用
总结
在实际应用中,我们需要根据业务的需要来采取相应的缓存措施,如上所述,html5的几种缓存都有各自的优缺点和适用场景,有时我们也需要组合使用。
关于HTML5缓存我们就介绍到这里。