我们经常会使用Cookie来存储键值队数据。但是Cookie本身存在一定的局限性。
Cookie本身的局限性:
所以对浏览器来说,使用 Web Storage 存储键值对比存储 Cookie 方式更好,而且容量更大。
Web Storage(本地存储)包含两种:localStorage 和 sessionStorage。
在浏览器的开发者工具下的Appliction中可以看到
就是这两个玩意。
两种存储方式的不同点:
1 .不同的存储时效:localStorage持久储存,除非主动删除数据,否则数据永远不会过期;sessionStorage会话结束(也就是网页关闭、浏览器关闭、或者说只标签页关闭)变会消失,而且存储的数据只能在同一个会话的页面才能访问。
2. 不同的存储容量:localStorage大小一般2-5M,sessionStorage存储大小不一样,一些浏览器不设限。
两种存储方式的相同点:
...Storage.setItem('key','value')
设置内容。...storage.getItem('key')
方法获取内容。...Storage.removeItem('key')
方法清除内容。...Storage.clear()
方法清除所有内容。...Storage.length
属性获取长度。...Storage.key(index)
方法获取对应的下标的键值对的键名。ps:localStorage和sessionStorage拥有相同的API,拥有不同的存储时效和容量
由于各个浏览器对于Storage都有限制,我们使用的时候要注意一下几点。
存储容量一旦超出限制,会抛出QuotaExceededError异常
**所以我们存储键值对时应该使用try{ ... }catch(e){ .... }
**来捕获异常。
Storage仅能存储字符串,所以在使用.setItem()设置值的时候
,浏览器会默认调用对象本身的toString()
方法,若没有,再调用浏览器的toString()
方法将任何值都转成字符串,再取数据的判断的时候注意类型。
//localStorage和sessionStorage的结果是一样的。
//**************************以下所有返回值都是String类型
localStorage.setItem('k1',false);
localStorage.getItem('k1'); // "false"
localStorage.setItem('k2',window.undefined);
localStorage.getItem('k2'); // "undefined"
localStorage.setItem('k3',null);
localStorage.getItem('k3'); // "null"
localStorage.setItem('k4',0);
localStorage.getItem('k4'); // "0"
localStorage.setItem('k5','');
localStorage.getItem('k5'); // ""
//空数组
localStorage.setItem('k6',[]);
localStorage.getItem('k6'); // ""
localStorage.setItem('k7',[1,2,3,4]);
localStorage.getItem('k7'); // "1,2,3,4"
//空对象
localStorage.setItem('k8',{});
localStorage.getItem('k8'); // "[object Object]"
localStorage.setItem('k9',{a:1});
localStorage.getItem('k9'); // "[object Object]"
localStorage.setItem('k10',{toString:function(){ return 'tostringed'; }});
localStorage.getItem('k10'); // "tostringed"
localStorage.setItem('k11',{toString: function(){ return 100;}});
localStorage.getItem('k11'); // "100"
从上面我们可以看出用Storage存储对象的话,会被统一转化成"[object Object]"
。
如果想使用Storage存储对象,方法如下
localStorage.setItem( 'k12',JSON.stringify({data:123}) );
localStorage.getItem('k12'); // "{"data":123}"
//使用 JSON.parse可以将字符串转换成对象
JSON.parse( localStorage.getItem('k12'); ) // {data:123} | object类型
功能:
(function(){
var ls = window.localStorage;
function oops(){ //浏览器不支持localStorage警告
return console.warn('your browser is not supported localStorage API');
}
function getItem(key){
var data = ls.getItem(key);
data = JSON.parse(data) || {}; //如果数据被情况,data会得到null,这里给他赋个空对象
if(data.time === 0){ //是持久化数据
return data.value;
}else if(Date.now() > data.time){ //数据过期
return '';
}else{
return typeof data.value !== 'undefined'? data.value : '';
}
}
function setItem(key,value,time){
if(typeof key === 'undefined')return;
var data = {
time: time? Date.now() + time : 0,
value : value
}
data = JSON.stringify(data);
try {
ls.setItem(key,data);
} catch(e){
ls.clear();
ls.setItem(key,data);
}
}
function removeItem(key){
ls.removeItem(key);
}
function clear(){
ls.clear();
}
window.myStorage = {
getItem: ls ? getItem : oops,
setItem: ls ? setItem : oops,
removeItem: ls ? removeItem : oops,
clear: ls ? clear : oops
}
})()
IndexedDB是HTML5规范里新出现的浏览器里内置的数据库。存储在IndexedDB里的数据是永久保存,不像cookies那样只是临时的。
indexedDB.open(name,verson)
创建或连接数据库(name为数据库名称,verson为数据库版本,数据库创建时不指定verson会默认为1)IDBOpenDBRequest
对象有3个重要回调函数
IDBOpenDBRequest
对象不是我们希望得到的DB对象,DB对象在result中,IDBOpenRequest.result
才是我们要操作数据库的DB对象。//例子
var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
var dbName = 'testDB',
verson = 1,
request;
function openDB(dbName){
request = indexedDB.open(dbName,verson);
request.onerror = function(e){
console.log(e.currentTarget.error.message);
}
request.onsuccess = function(){
db = request.result;
console.log('success open');
}
}
openDB(dbName);
由于已经创建了verson为1的数据库,我们再次打开相同数据库,并修改verson为2
var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
var dbName = 'testDB',
verson = 2, //版本号升级了
request;
function openDB(dbName){
request = indexedDB.open(dbName,verson);
request.onerror = function(e){
console.log(e.currentTarget.error.message);
}
request.onsuccess = function(){
db = request.result;
console.log('success open');
}
request.onupgradeneeded = function(){
console.log('DB verson changed to '+verson);
}
}
openDB(dbName);
使用 indexedDB.open 连接数据库成功后会返回一个 IDBOpenDBRequest 对象,我们可以调用该对象的 close 方法来关闭数据库。
var request = indexedDB.open('testDB',1);
var db = request.result;
db.close();
indexedDB.deleteDatabase(name); //name为数据库名称
//例子
//....
var obj = indexedDB.deleteDatabase('DB1');
console.log(obj);
PS:建议尽量在控制台中删除数据库
onupgradeneeded
回调函数中创建新的objectStoreIDBOpenRequest.result.createObjectStore(osName, keyType); //osName为表名,{autoIncrement: true}设置主键自增
keyType的两种类型
创建objectStore
//例子
var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
var dbName = 'testDB',
verson = 3,
storeName = 'os1',
request,
db;
function openDB(dbName){
request = indexedDB.open(dbName,verson);
request.onerror = function(e){
console.log(e.currentTarget.error.message);
}
request.onsuccess = function(){
db = request.result;
console.log('success open');
}
request.onupgradeneeded = function(){
db = request.result;
if(!db.objectStoreNames.contains('os1')){ //当要创建的表不存在时
db.createObjectStore('os1',{keyPath: 'id'});
}
console.log('DB verson changed to '+verson);
}
}
openDB(dbName);
创建objectStore这个动作必须要放在onupgradeneeded
中,如果在onsuccess
中创建会报错Failed to execute 'createObjectStore' on 'IDBDatabase'
request.onsuccess = function(){
db = request.result;
if(!db.objectStoreNames.contains('os1')){ //当要创建的表不存在时
db.createObjectStore('os1',{keyPath: 'id'});
}
console.log('success open');
}
在对数据库做任何事(数据表的增、删、改、查)都需要通过事务。
indexedDB—>transaction—>objectStore
var transaction = db.transaction(storeName, 'readwrite');
var store = transaction.objectStore(storeName);
这样我们就得到了"数据表",也就是IDBObjectStore
对象IDBObjectStore.add
IDBObjectStore.get
IDBObjectStore.getAll
IDBObjectStore.put
IDBObjectStore.delete
IDBObjectStore.clear
PS:add 和 put 的作用类似,区别在于 put 保存数据时,如果该数据的主键在数据库中已经有相同主键的时候,则会修改数据库中对应主键的对象,而使用 add 保存数据,如果该主键已经存在,则保存失败。
以上操作会返回一个 IDBRequest
对象
IDBRequest.onsuccess=function(){}
绑定完成事件;IDBRequest.onerror-function(){}
绑定失败事件;IDBRequest.result
获取查询结果。增加数据IDBObjectStore.add()
//indexed.open()打开数据库部分略
var data = [
{
name: '穆',
id: 1,
job: '白羊座黄金圣斗士',
},
{
name: '阿鲁迪巴',
id: 2,
job: '金牛座黄金圣斗士',
},
{
name: '撒加',
id: 3,
job: '双子座黄金圣斗士',
},
{
name: '迪斯马斯克',
id: 4,
job: '巨蟹座黄金圣斗士',
},
{
name: '艾奥里亚',
id: 5,
job: '狮子座黄金圣斗士',
}
{
name: '沙加',
id: 6,
job: '处女座黄金圣斗士',
}
];
function addData(){
var transaction = db.transaction(storeName, 'readwrite');
var store = transaction.objectStore(storeName);
data.map(function(o){
store.add(o);
});
}
PS:addData()
这个操作,只要indexed.open
打开数据库后,随便那都可以调用
function getData(id){
var transaction = db.transaction(storeName, 'readwrite');
var store = transaction.objectStore(storeName);
var request = store.get(id);
request.onsuccess =function(){
console.log(request.result);
}
}
function getAllData(){
var transaction = db.transaction(storeName, 'readwrite');
var store = transaction.objectStore(storeName);
var request = store.getAll();
request.onsuccess = function(){
console.log(request.result);
}
}
function updateData(data){
if(typeof data !== 'object')return;
var transaction = db.transaction(storeName, 'readwrite');
var store = transaction.objectStore(storeName);
var request = store.put(data);
request.onsuccess = function(){
console.log(request.result);
}
}
function deleteData(id){ //删除指定数据
var transaction = db.transaction(storeName, 'readwrite');
var store = transaction.objectStore(storeName);
var request = store.delete(id);
request.onsuccess = function(){
console.log(request.result);
}
}
function deleteAllData(){ //删除所有数据
var transaction = db.transaction(storeName, 'readwrite');
var store = transaction.objectStore(storeName);
var request = store.clear();
request.onsuccess = function(){
console.log(request.result);
}
}
使用 IDBObjectStore,get()
和 IDBObjectStore,getAll()
这两个查询方法我们只能根据主键或者搜索全部数据。我们如果需要根据数据中任意一个字段的值来查询数据,这就用到索引了。
索引的意义在于,可以根据任意的字段的值查询数据,从任意的字段拿到数据。
索引中的数据是和objectStore同步的,索引中的数据被修改,objectStore中也一样被修改
索引是在创建objectstore的时候创建的。
IDBObjectStore.createIndex(indexName,KeyPath,optionPara);
IDBObjectStore.add()
的操作是失败的,objectStore中会没有数据) var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
var dbName = 'testDB',
verson = 1,
storeName = 'os1',
request,
db;
function openDB(dbName){
request = indexedDB.open(dbName,verson);
request.onerror = function(e){
console.log(e.currentTarget.error.message);
}
request.onsuccess = function(){
db = request.result;
console.log('success open');
addData();
}
request.onupgradeneeded = function(){
var store = null;
db = request.result;
if(!db.objectStoreNames.contains(storeName)){ //当要创建的表不存在时
store = db.createObjectStore(storeName,{autoIncrement: true}); //创建objectStore
store.createIndex('idIndex','id',{unique: true}); //创建id索引
store.createIndex('attrIndex','attr',{unique: false}); //创建星座属性索引
store.createIndex('labelIndex','label',{multiEntry: true}); //创建角色标签索引
}
console.log('DB verson changed to '+verson);
}
}
openDB(dbName);
var data = [
{
name: '穆',
id: 1,
job: '白羊座黄金圣斗士',
attr: '火',
label: ['肉盾','反伤','群攻','念攻'],
},
{
name: '阿鲁迪巴',
id: 2,
job: '金牛座黄金圣斗士',
attr: '土',
label: ['肉盾','单体','物攻']
},
{
name: '撒加',
id: 3,
job: '双子座黄金圣斗士',
attr: '风',
label: ['群攻'],
},
{
name: '迪斯马斯克',
id: 4,
job: '巨蟹座黄金圣斗士',
attr: '水',
label: ['群攻','念攻'],
},
{
name: '艾奥里亚',
id: 5,
job: '狮子座黄金圣斗士',
attr: '火',
label: ['群攻','物攻'],
},
{
name: '沙加',
id: 6,
job: '处女座黄金圣斗士',
attr: '土',
label: ['群攻','念攻','控制'],
},
{
name: '童虎',
id: 7,
job: '天秤座黄金圣斗士',
attr: '风',
label: ['群攻','念攻'],
},
{
name: '米罗',
id: 8,
job: '天蝎座黄金圣斗士',
attr: '水',
label: ['单体','物攻','爆发'],
},
{
name: '艾俄洛斯',
id: 9,
job: '射手座黄金圣斗士',
attr: '火',
label: ['单体','物攻','爆发'],
},
{
name: '修罗',
id: 10,
job: '摩羯座黄金圣斗士',
attr: '土',
label: ['单体','多段','物攻','爆发'],
},
{
name: '卡妙',
id: 11,
job: '水瓶座黄金圣斗士',
attr: '风',
label: ['控制','念攻'],
},
{
name: '阿布罗狄',
id: 12,
job: '双鱼座黄金圣斗士',
attr: '水',
label: ['单体','多段','物攻','爆发'],
},
];
function addData(){
var transaction = db.transaction(storeName, 'readwrite');
var store = transaction.objectStore(storeName);
data.map(function(o){
store.add(o);
});
}
IDBobjectStore.index('游标名称')
获取的,也就是说也要通过事务索引可以按值搜索,但是**索引不能操作数据,而游标可以**
//获取所有标签为 群攻 的圣斗士的信息
function useIndexGetAllData(){
var transaction = db.transaction(storeName, 'readwrite'),
store = transaction.objectStore(storeName),
index = store.index('labelIndex');
request = index.getAll('群攻');
request.onsuccess = function(){
console.log(request.result);
}
}
//获取一条标签为 群攻 的圣斗士信息
function useIndexGetData(){
var transaction = db.transaction(storeName, 'readwrite'),
store = transaction.objectStore(storeName),
index = store.index('labelIndex');
request = index.get('群攻');
request.onsuccess = function(){
console.log(request.result);
}
}
如果我们想要获取objectStore中一个指定区间的数据而不是所有数据,可以使用游标。或者是操作通过索引查询出的数据,也可以使用游标。
也就是说游标也就是一个可以增强增删改查的工具,objectStore和索引都可以创建游标
IDBObjectStore/IDBIndex.openCursor(range,direction); //也就是说还是要通过事务
Range | Code |
---|---|
all keys <= x | upperBound(x) |
all keys < x | upperBound(x,true) |
all keys >= y | lowerBound(y) |
all keys > y | lowerBound(y,true) |
the key = z | only(z) |
x<=all keys <=y | bound(x,y) |
xbound(x,y,true,true) |
|
xbound(x,y,ture,false) |
|
x<=all keys bound(x,y,false,true) |
|
function useCursorGetData(){
var transaction = db.transaction(storeName, 'readwrite');
var store = transaction.objectStore(storeName);
request = store.openCursor(); //创建游标,并且不带参数
request.onsuccess = function(){
var cursor = request.result; //获取游标
if(cursor){
console.log(cursor.value);
cursor.continue(); //遍历数据
}
}
}
function useCursorGetData(id){
var transaction = db.transaction(storeName, 'readwrite');
var store = transaction.objectStore(storeName);
request = store.openCursor(IDBKeyRange.only(id)); //创建游标
request.onsuccess = function(){
var cursor = request.result; //获取游标
if(cursor){
console.log(cursor.value);
cursor.continue(); //遍历数据
}
}
}
function useCursorGetData(){
var transaction = db.transaction(storeName, 'readwrite');
var store = transaction.objectStore(storeName);
request = store.openCursor(null,'prev'); //创建游标
request.onsuccess = function(){
var cursor = request.result; //获取游标
if(cursor){
console.log(cursor.value);
cursor.continue(); //遍历数据
}
}
}
案例1
给前六宫的黄金圣斗士打上现在召唤的标签
function useIndexAndCursorOperateData1(){
var transaction = db.transaction(storeName, 'readwrite');
var store = transaction.objectStore(storeName);
var index = store.index('idIndex');
var request = index.openCursor(IDBKeyRange.bound(1,6,false,false)); //在索引下也是可以创建游标的 效果和 store.openCursor() 一样
request.onsuccess = function(){
var cursor = request.result;
if(cursor){
var value = cursor.value;
value.label.push('限时召唤'); //给前6个圣斗士打上 “限时召唤” 的标签
cursor.update(value);
console.log(cursor);
cursor.continue();
}
}
}
案例2
给标签带有念攻的角色加上念力穿透这个标签
function useIndexAndCursorOperateData2(){
var transaction = db.transaction(storeName ,'readwrite');
var store = transaction.objectStore(storeName);
var index = store.index('labelIndex');
var request = index.openCursor(IDBKeyRange.only('念攻'));
request.onsuccess = function(){
var cursor = request.result;
if(cursor){
var value = cursor.value;
value.label.push('念力穿透');
cursor.update(value);
console.log(cursor);
cursor.continue();
}
}
总结:索引是ObjectStore下的,需要通过ObjectStore才能获取指定的索引,而游标是一个强化查询(增删改查)的工具,ObjectStore和索引都可以用。
IndexedDB的优势
Web Storage的优势
我使用的是wamp,apache的版本是2.4.27。
找到wamp的安装目录下wamp\bin\apache\apache2.4.27\conf的mime.types文件。
添加一行text/cache-manifest appcache
(一般情况都是已经有的)
配置好后浏览器才会认为manifest文件是个清单文件。
manifest.appcache文件(文件名随便起,但是后缀要是.appcache)
CACHE MANIFEST
#VERSION 1.0
CACHE:
./public/img/test.png
./public/js/test.js
FALLBACK:
NETWORK:
*
在html标签中加上manifest属性
<html lang="en" manifest="manifest.appcache">
<head>
<meta charset="UTF-8">
<title>测试title>
head>
<body>
<img src="./public/img/test.png" alt="">
<script src="./public/js/test.js">script>
body>
html>
关闭服务器后,访问该页面,是依旧可以访问的。
所以不推荐使用application cache做离线存储
service worker的诸多功能
1、离线缓存
2、消息推送
3、后台消息传递
4、网络代理,转发请求,伪造响应
service worker 离线存储功能的优势
1、可以更细致的控制存储资源
2、拥有强大的更新机制
但是目前service worker的兼容性不好。而且需要https协议。
service worker的示例
https://mdn.gothub.io/sw-test/