1.本地存储-Web Storage
2.本地存储-IndexedDB
3.本地存储的扩展介绍
4.离线存储-app cache
5.总结
分析存储需求:
- 移动端网络环境因素
- 数据响应慢,体验下降,2G/3G网速非常慢
- 流量因素
- 客户端存储 = 节省流量 = 节省金钱
- 追求原生体验
- 离线使用应用
- 存储应用相关资源、数据
Cookie 可行否? 否
因为Cookie 的局限性:
- 存储大小限制,仅 4kb 左右
- 单个域名下的数量限制,50个左右
- 污染请求头,浪费流量
说明:
- cookie的作用是与服务器进行交互,作为HTTP规范的一部分而存在 ,
而Web Storage仅仅是为了在本地“存储”数据而生。 - 每次你请求一个新的页面的时候Cookie都会被发送过去,这样无形中浪费了带宽。
本地存储:localStorage 和 sessionStorage
- 相同的使用方法
- 使用 setItem(key,value) 方法设置存储内容
- 使用 getItem(key) 方法获取存储内容
- 使用 removeItem(key) 方法删除存储内容
- 使用 clear() 方法清除所有内容
- 使用 length 属性获取存储内容个数
- 使用 key(index) 方法获取存储字段
- 不同的存储时效
- localStorage 存储会持久化
- sessionStorage 存储会在网页会话结束(标签页的关闭)后失效,只有在同一个会话中的页面才能访问。
- 不同的存储容量
- localStorage 容量一般在 2 – 5Mb 左右
- sessionStorage 存储容量不一,部分浏览器不设限
- Web Storage Support Test
http://dev-test.nemikor.com/web-storage/support-test/
sessionStorage和localStorage的存储空间虽然较大,但是存储容量仍有限制,叫做配额。
- 使用 Storage 时的注意点:
- 存储容量超出限制
- 抛出 QuotaExceededError 异常 (存储值时应使用 try catch 捕获异常)
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>quota exceed test</title>
</head>
<body>
<button class="start-btn">start</button>
<script>
var btn = document.querySelector('.start-btn');
var data = '01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890',
id = 0,
len = data.length,
total = 0,
key;
function save() {
id = (id + 1) % 10;
key = Date.now() + '' + id;
// try...catch 主要用于捕获代码运行时的异常,并进行异常处理。
// try 部分包含运行时,可能出现异常的代码.
// 而 catch 部分包含错误发生时运行的代码。
try {
// try 中写可能产生异常的语句
localStorage.setItem(key, data);
save();
} catch (e) {
// catch 中写负责异常处理的语句
console.log(e.name); //QuotaExceededError
}
}
btn.addEventListener('click', save);
</script>
</body>
</html>
- 存储类型的限制
- 仅能存储字符串
- 注意类型转换
JSON.stringify
、JSON.parse
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>quota exceed test</title>
</head>
<body>
<button class="str" onclick="save('abc')">字符串 abc</button><br />
<button class="bool" onclick="save(false)">布尔值 false</button><br />
<button class="undef" onclick="save(window.undefined)">未定义 window.undefined</button><br />
<button class="null" onclick="save(null)">空值 null</button><br />
<button class="num" onclick="save(0)">数字 0</button><br />
<button class="arr1" onclick="save([])">数组 []</button><br />
<button class="arr2" onclick="save([1,2,3])">数组 [1,2,3]</button><br />
<button class="obj1" onclick="save({})">对象 {}</button><br />
<button class="obj2" onclick="save({x: 1})">对象 {x: 1}</button><br />
<button class="obj3" onclick="save({toString: function () {return 'toStringed'}})">对象 {toString: function () {return 'toStringed'}}</button><br />
<button class="obj4" onclick="save({toString: function () {return 100}})">对象 {toString: function () {return 100}}</button><br />
<script>
function save (data) {
localStorage.setItem('key', data)
show(localStorage.getItem('key'))
}
function show (value) {
console.log(value, '|', typeof value)
}
</script>
</body>
</html>
打印结果:
> abc | string
> false | string
> undefined | string
> null | string
> 0 | string
> | string
> 1,2,3 | string
> [object Object] | string
> [object Object] | string
> toStringed | string
> 100 | string
类型转换:在控制台输入
> localStorage.setItem('key',JSON.stringify({data:1}))
< undefined
> localStorage.getItem('key')
< "{"data":1}"
> JSON.parse(localStorage.getItem('key'))
< {data: 1}
- sessionStorage 失效机制
- 刷新页面并不会失效
location.reload()
- 相同 URL 不同标签页不能共享 sessionStorage
- Web Storage 的优化
性能与存储容量大小无关,与读取次数有关
- 减少读取 item 次数
- 单个 item 中尽可能多的存储数据
- 带有过期机制的 localStorage 的功能需求
- 可以设置数据的存储时间
- 过期数据清理
- 自行维护存储空间
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>cache storage</title>
</head>
<body>
<script>
'use strict'
;(function () {
var ls = window.localStorage
function oops () {
return console.warn('your browser is not supported localStorage API')
}
function getItem (key) {
var data = ls.getItem(key) //在没有数据项的情况下,data为null
data = JSON.parse(data)||{} //将字符串类型的data转为对象类型的data
if (data.time === 0) {// 上句如是data = JSON.parse(data),在data.time时,就会报错。所以调整成data = JSON.parse(data)||{}
// 持久化数据直接返回
return data.value
} else if (Date.now() > data.time) { // 判断是否超时
//过期
ls.removeItem(key)
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) //stringify方法进行对象的字符串序列化
try {
ls.setItem(key, data)
} catch (e) {
ls.clear()
ls.setItem(key, data)
}
}
function removeItem (key) {
ls.removeItem(key)
}
function clear () {
ls.clear()
}
window.cacheStorage = {//浏览器支持使用localStorage,不支持输出提示语
getItem: ls ? getItem : oops,
setItem: ls ? setItem : oops,
removeItem: ls ? removeItem : oops,
clear: ls ? clear : clear
}
})()
</script>
</body>
</html>
IndexedDB 数据库
1.了解indexedDB数据库
1)indexedDB数据库是一种事务型数据库
2)是NoSQL数据库
3)使用JS对象存储数据
2.如何创建数据库和表
- 如何创建数据库和"表"?
-
indexedDB.open('dbName',dbVersinNumber)
创建数据库,返回 IDBOpenDBRequest 对象 - indexedDB.createObjectStore 创建“表”
-
indexedDB.deleteDatabase('dbName')
删除数据库
function createDB() {
// request 是 IDBOpenDBRequest对象。
request = db.open(dbName, version)
//请求有三种状态,如下:
request.onsuccess = function() { // 打开数据库成功
db = request.result;
console.log('open success');
}
request.onerror = function(e) { // 打开数据库失败
console.log(e.currentTarget.errormessage)
}
request.onupgradeneeded = function(e) { //请求数据库版本变化时
var store = null;
db = e.target.result;
console.log('upgradeneeded');
/*
if (!db.objectStoreNames.contains(osName)) {
db.createObjectStore(osName, {autoIncrement: true}) // 创建的表的主建是自增型的
}
*/
if (!db.objectStoreNames.contains(osName)) {
store = db.createObjectStore(osName, { keyPath: 'id' }) // 创建的表以字段为主建
store.createIndex('idIndex', 'id', { unique: true }); //创建索引字段id唯一
store.createIndex('categoryIndex', 'category', { multiEntry: true }); //创建索引字段为数组
store.createIndex('hpIndex', 'hp', { unique: false });
}
}
// onsuccess事件在onupgradeneeded事件之后触发
}
- 设置主键的两种方法
- 设置自增主键 - {autoIncrement: true}
db.createObjectStore(osName, {autoIncrement: true})
- 取数据中字段作为主键 - {keyPath: 字段名}
store = db.createObjectStore(osName, { keyPath: 'id' })
3. 关于表的增删改查
- 如何使用事务获取表
调用IDBDatabase.transaction
方法会返回一个 IDBTransaction 对象,
它含有一个 objectStore 方法, 可以让用户通过指定模式操作数据库中的“表”。
indexedDB
-> transaction
-> objectStore
//osName 表格名称,
var db = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
var transaction = db.transaction(osName, 'readwrite'), //打开一个事务,读写模式
store = transaction.objectStore(osName);
- 事务的模式
- 读写模式 -
readwrite
- 只读模式(默认) -
readonly
- 版本变更模式 -
versionchange
- 关于“表”的增删查改的相关方法
- 增加数据 –
IDBObjectStore.add
- 获取数据 –
IDBObjectStore.get
- 获取所有数据 –
IDBObjectStore.getAll
- 修改(或新增)数据 –
IDBObjectStore.put
- 删除数据 –
IDBObjectStore.delete
- 清除所有数据 –
IDBObjectStore.clear
这些方法都返回一个IDBRequest
对象。
function addData() {
if (!db) { alert("db"); }
// 调用 `IDBDatabase.transaction` 方法会返回一个 IDBTransaction 对象,
// 它含有一个 objectStore 方法, 可以让用户通过指定模式操作数据库中的“表”。
var transaction, store;
transaction = db.transaction(osName, 'readwrite'); //打开一个事务,读写模式
store = transaction.objectStore(osName) ///获取osName指定的 object store
data.map(function(o) {
store.add(o)
// request = store.add(o) //增加数据
// if (data.length - 1 === i) {
// request.onsuccess = function () {
// console.log('alreay add all data to db')
// showCurrentData()
// }
// }
})
}
function getData(id) {
var transaction = db.transaction(osName, 'readwrite'), //打开一个事务,读写模式
store = transaction.objectStore(osName);
var request = store.get(id);
request.onsuccess = function() {
console.log(request.result);
}
}
function getAllData() {
var transaction = db.transaction(osName, 'readwrite'), //打开一个事务,读写模式
store = transaction.objectStore(osName);
var request = store.getAll();
request.onsuccess = function() {
console.log(request.result);
}
}
function updateData(id) {
var transaction = db.transaction(osName, 'readwrite'), //打开一个事务,读写模式
store = transaction.objectStore(osName);
var request = store.get(id);
request.onsuccess = function() {
request = store.put({ //更新即可以更新也可以添加,取决于关键字是否有重复
name: '小花猫',
id: id,
hp: 9
});
}
}
function deleteData(id) {
var transaction = db.transaction(osName, 'readwrite'), //打开一个事务,读写模式
store = transaction.objectStore(osName);
var request = store.delete(id);
request.onsuccess = function() {
console.log('delete success');
}
}
function clear() {
var transaction = db.transaction(osName, 'readwrite'), //打开一个事务,读写模式
store = transaction.objectStore(osName);
var request = store.clear();
request.onsuccess = function() {
console.log('clear success');
}
}
- IDBRequest 对象
- 使用
IDBRequest.onsuccess
执行查询完成回调 - 使用
IDBRequest.result
获取查询结果 - 使用
IDBRequest.onerror
执行查询失败回调
4. 关于索引
- 如何创建索引
IDBObjectStore.createIndex
- indexName: 索引名称
- keyPath: 索引字段,可以为空或者数组(type array)
- optionParameters: 索引配置参数
store = db.createObjectStore(osName, { keyPath: 'id' }) // 创建的表以字段为主建
store.createIndex('idIndex', 'id', { unique: true }); //创建索引字段id唯一
store.createIndex('categoryIndex', 'category', { multiEntry: true }); //创建索引字段为数组
store.createIndex('hpIndex', 'hp', { unique: false });
- optionParameters: 索引配置参数
- unique 表示keyPath字段的数据是否唯一
2)multiEntry 表示是否为 keyPath 字段的每一项建立一条索引数据
- 使用索引的好处
- 可以使用存储记录中的值进行检索
- 索引自动更新
- 索引数据自动排序
- 索引的相关方法
1)查询数据 - IDBIndex.get
- 查询所有数据 - IDBIndex.getAll
- 打开游标 - IDBIndex.openCursor
function useIndexGetData() { // 索引只能查询数据,并不能操作数据
var transaction = db.transaction(osName, 'readwrite'), //打开一个事务,读写模式
store = transaction.objectStore(osName),
index = store.index('categoryIndex');
request = index.getAll('飞行');
request.onsuccess = function() {
console.log(request.result);
}
}
5. 关于游标
- 如何创建游标?
IDBObjectStore/IDBIndex.openCursor
- 接收可选参数 range 和 direction,指明游标遍历的范围和方向
- 返回一个 IDBRequet 对象,异步方法
- 该 IDBRequet 对象的结果是一个 IDBCursor 对象
- IDBKeyRange 对象
1)upperBound: 指定游标范围的上限
2)lowerBound: 指定游标范围的下限
3)bound: 指定游标范围的区间
4)only: 指定游标的值
key range 取值表:
- direction 参数
- next: 顺序查询
- nextunique: 顺序唯一查询
- prev: 逆序查询
- prevunique: 逆序唯一查询
function useCursorGetData() {
var transaction = db.transaction(osName, 'readwrite'), //打开一个事务,读写模式
store = transaction.objectStore(osName),
// request=store.openCursor(); //创建游标
// request=store.openCursor(IDBKeyRange.only('002')); //指定游标KeyRange,only一个
// request = store.openCursor(null, 'prev');
request = store.openCursor(IDBKeyRange.lowerBound('002'), 'prev'); //逆序查询,游标范围下限是"002"
request.onsuccess = function() {
var cursor = request.result;
if (cursor) {
console.log(cursor.value);
cursor.continue();
}
}
}
6. 索引和游标的结合使用
- 索引和游标的优势
索引:可以按值搜索
游标:可以选择遍历顺序及操作数据
function useIndexAndCursorOperateData1() { // 索引和游标结合,可以查询和操作数据
var transaction = db.transaction(osName, 'readwrite'), //打开一个事务,读写模式
store = transaction.objectStore(osName),
index=store.index('categoryIndex');
request=index.openCursor();
request.onsuccess = function() {
var cursor = request.result;
if (cursor) {
if(cursor.value.id==='002'){
/* // 更新数据
cursor.update({
name: '小蝙蝠',
id: '002',
hp: 10,
category:['怪物','飞行']
})
*/
// 删除数据
cursor.delete().onsuccess=function () {
console.log('delete success');
}
}
// 更新数据和删除数据操作都是异步操作,所以输出可能会混乱
console.log(cursor.value);
cursor.continue();
}
}
}
function useIndexAndCursorOperateData() { // 索引和游标结合,可以查询和操作数据
var transaction = db.transaction(osName, 'readwrite'), //打开一个事务,读写模式
store = transaction.objectStore(osName),
index = store.index('hpIndex');
// request = index.openCursor(IDBKeyRange.upperBound(5)); // 5(包含5)以下
request = index.openCursor(IDBKeyRange.bound(5,10,true,true)); // 范围(5到10)
request.onsuccess = function() {
var cursor = request.result,value=null;
if (cursor) {
value = cursor.value;
value.hp += 20;
cursor.update(value);
console.log(cursor.value);
cursor.continue();
}
}
}
IndexedDB 与 Web Storage 比较
- indexedDB 的优势:
- 存储类型更加丰富
- 可实现高级查询
- 可在 Web Workers 中使用
- 存储容量更大
- Web Storage 的优势
- API 较少,更容易掌握
- 兼容性更好
- IndexedDB 的兼容性问题
1)IOS8 & 9 中 webview 不支持 indexedDB
2)Firefox 单次存储 Blob 数据超 50Mb 会抛出异常, 这个50M可以在IndexDB的一些API中进行修改
3)Safari 的 indexedDB 不能用于 web workers
4)Chrome36 不支持存储类型的数据
其他存储方式介绍
1. WebSQL
- 关系型数据库
- 随HTML5规范加入, 在浏览器端运行的轻量级数据库。
- WebSQL 的 API 有哪些?
- openDatabase: 打开数据库
- transaction: 获取事务,进行数据库操作
- executeSql: 执行 SQL 进行查询
- WebSQL 的现状
-
兼容性问题严重
W3C 已经不再积极处理其相关规范(避免使用WebSQL)
2. Filesystem & FileWriter API
- 供我们在客户端进行文件的存储
-
但是和WebSQL有相似的命运,有严重的兼容性问题和规范被废弃(避免使用)。
3. UserData
是IE独有的存储方式:
- 只在Windows系统的IE中存在
- 容量不大
4. Cookie
- 存储大小限制,仅 4kb 左右
- 单个域名下的数量限制,50个左右
- 污染请求头,浪费流量