客户端数据存储概述
因为浏览器无法记住请求的信息,因此所有的信息必须从服务器端获得。
使用客户端存储的优势是
- 能够直接访问数据,响应速度更快
- 能够减少网络访问的流量,减轻服务器的压力
其不足处有
- 无法提供像数据库那样强大的搜索功能
- 无法自动更新
- 存储容量大小模糊无法控制
使用Cookie
cookie是通过HTTP请求头发送到服务器的。在默认情况下,浏览器没有限制Cookie的数量。
Cookie的使用
创建Cookie
创建普通的Cookie
<script>
var name = "Mickle jiang"
// 创建一个名为name值为Mickle jiang的Cookie
// Cookie的值必须符合 URL编码规则,
//这意味着如果想动态定义 Cookie,那么就需要使用类似 encodeURIComponent的辅助函数
document.cookie = "username=" + encodeURIComponent(name)
// 创建一个名为age, 值为20的Cookie
document.cookie = "age=20"
</script>
创建带有过期时间的Cookie
// Cookie默认的有效期是当前会话
document.cookie = "name=jiangyx; expries=Fri, 31 Dec 9999 23:59:59 GMT"
创建带有指定子域名的Cookie
// Cookie默认是只对当前域名有效
document.cookie = "name=jiangyx; domain=blog.jiangyx.com"
读取Cookie
// 读取Cookie直接使用document.cookie即可获取
console.log(document.cookie)
删除Cookie
// 删除Cookie就是将其有效期设置为过期时间即可
document.cookie = "name=jiangyx; expires=Thu, 01 Jan 1970 00:00:00 GMT";
Cookie操作工具类
var cookieUtil = {
getItem: function (sKey) {
if (!sKey) { return null; }
return decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null;
},
setItem: function (sKey, sValue, vEnd, sPath, sDomain, bSecure) {
if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) { return false; }
var sExpires = "";
if (vEnd) {
switch (vEnd.constructor) {
case Number:
sExpires = vEnd === Infinity ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT" : "; max-age=" + vEnd;
break;
case String:
sExpires = "; expires=" + vEnd;
break;
case Date:
sExpires = "; expires=" + vEnd.toUTCString();
break;
}
}
document.cookie = encodeURIComponent(sKey) + "=" + encodeURIComponent(sValue) + sExpires + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "") + (bSecure ? "; secure" : "");
return true;
},
removeItem: function (sKey, sPath, sDomain) {
if (!this.hasItem(sKey)) { return false; }
document.cookie = encodeURIComponent(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT" + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "");
return true;
},
hasItem: function (sKey) {
if (!sKey) { return false; }
return (new RegExp("(?:^|;\\s*)" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=")).test(document.cookie);
},
keys: function () {
var aKeys = document.cookie.replace(/((?:^|\s*;)[^\=]+)(?=;|$)|^\s*|\s*(?:\=[^;]*)?(?:\1|$)/g, "").split(/\s*(?:\=[^;]*)?;\s*/);
for (var nLen = aKeys.length, nIdx = 0; nIdx < nLen; nIdx++) { aKeys[nIdx] = decodeURIComponent(aKeys[nIdx]); }
return aKeys;
}
};
Cookie实战
简单地使用 Cookie 统计你访问网站的次数
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<p>当前网站访问人数: <span id="count"></span></p>
</body>
<script type="text/javascript" src="js/cookie.js" ></script>
<script>
var count = 0
if (cookieUtil.hasItem('views')) {
count = parseInt(cookieUtil.getItem('views'))
}
count++
// 更新访问人数
cookieUtil.setItem('views', count)
document.getElementById('count').innerText = count
</script>
</html>
使用Web存储
Web存储
Web存储即本地存储分为两个版本,本地存储(Local Storge)和会话存储(Session Storge)。
本地存储能够持久的存储在浏览器中,会话存储是当浏览器关闭后就会清除。
Web存储是和域名一一对应的。和Cookie不同,在Web存储中,子域名和主域名是隔离的,即www.app.com和blog.app.com中Web存储的数据是隔离不互通的。
Web存储没有容量大小的限制,但是一般为5MB-10MB之间。
使用Web存储
// 设置特定键的值
localStorage.setItem('name', 'mickle')
// 检索特定键的值
console.log(localStorage.getItem('name')) // mickle
// 删除键及与其关联的值
localStorage.removeItem('name')
console.log(localStorage.getItem('name')) // null
// 删除所有的键 / 值对(但只限于发出请求的特定域名)
localStorage.clear()
// 本地存储存储数组的字符串版本
localStorage.setItem('pi', ['222', '333'])
console.log(localStorage.getItem('pi'))
localStorage.setItem('pi2', JSON.stringify(['222', '333']))
// 通过JSON能够维持存储数据的结构
console.log(JSON.parse(localStorage.getItem('pi2')))
使用sessionStorge存储方式和localStorge方式一致
实战代码
使用本地存储来实现访问人数的统计
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<p>当前网站访问人数: <span id="count"></span></p>
</body>
<script>
if (window.localStorage) {
var count = localStorage.getItem('count')
if (!count) {
count = 0
}
count++;
localStorage.setItem('count', count)
document.getElementById('count').innerText = count
}
</script>
</html>
使用IndexedDB
检查IndexedDB的支持
function isIndexDBSupport() {
return 'indexedDB' in window
}
IndexDB在IOS8上的支持存在问题,因此可以做进一步的限制
function isIndexDBSupport() {
return 'indexedDB' in window && !/iPad|iPhone|iPod/.test(navigator.platform)
}
连接IndexedDB
<script>
function isIndexDBSupport() {
return 'indexedDB' in window && !/iPad|iPhone|iPod/.test(navigator.platform)
}
var db = null;
$(function() {
// 检查浏览器是否支持 IndexedDB
if (!isIndexDBSupport()) return;
// 用 indexedDB.open 方法打开数据库,并指明版本号(从1开始)
var openRequest = indexedDB.open('db1', 1)
openRequest.onupgradeneeded = function(e) {
console.log('数据库更新监听')
}
openRequest.onsuccess = function(e) {
console.log('数据库连接成功')
// 获取到数据库连接对象
db = e.target.result
}
openRequest.onerror = function(e) {
console.log('数据库连接异常')
console.dir(e)
}
})
</script>
对象存储
indexedDB中的对象存储类似于SQL中的表
创建对象存储
对象存储的创建应该是在onupgradeneeded中执行。
一般创建对象存储方式为:
- 更新版本号
- 在onupgradeneeded中判断对象是否存在
- 不存在则使用createObjectStore进行创建对象存储
<script>
function isIndexDBSupport() {
return 'indexedDB' in window && !/iPad|iPhone|iPod/.test(navigator.platform)
}
var db = null;
$(function() {
// 检查浏览器是否支持 IndexedDB
if (!isIndexDBSupport()) return;
// 用 indexedDB.open 方法打开数据库,并指明版本号(从1开始)
var openRequest = indexedDB.open('db2', 1)
openRequest.onupgradeneeded = function(e) {
console.log('数据库更新监听')
var thisDB = e.target.result
if (!thisDB.objectStoreNames.contains('first')) {
// 如果不存在对象则创建
console.log('创建存储对象')
thisDB.createObjectStore('first')
}
}
openRequest.onsuccess = function(e) {
console.log('数据库连接成功')
// 获取到数据库连接对象
db = e.target.result
// 打印所有的存储对象
console.log(db.objectStoreNames)
}
openRequest.onerror = function(e) {
console.log('数据库连接异常')
console.dir(e)
}
})
</script>
定义对象存储的主键
和数据库中的表拥有主键一样,对象存储也需要设置一个主键用来进行唯一标识。
对象存储主键有两种设置方式
一、设置key path。它本质上是一个永远存在并且包含唯一信息的属性
二、使用 key generator,它本质上是一种生成唯一值的方式
openRequest.onupgradeneeded = function(e) {
console.log('数据库更新监听')
var thisDB = e.target.result
if (!thisDB.objectStoreNames.contains('people')) {
// 创建了一个名为 people 的对象存储,并假定每条数据都包含一个名为 email 的唯一属性
thisDB.createObjectStore('people', {keyPath: 'email'})
}
if (!thisDB.objectStoreNames.contains('notes')) {
// 创建了一个名为 notes 的对象存储,并使用一个自增值自动为主键赋值
thisDB.createObjectStore('notes', {autoIncrement: true})
}
if (!thisDB.objectStoreNames.contains('logs')) {
// 创建了一个名为 logs 的对象存储。这一次,使用自增值并将其存储在一个名为 id的属性中
thisDB.createObjectStore('logs', {autoIncrement: true, keyPath: 'id'})
}
}
如果存储的数据有一个属性应该唯一,那么使用 keyPath选项确保唯一性。如果存储的数据本身没有唯一属性,那么就使用自增值
定义对象存储的索引
在创建对象存储的主键后可以定义索引,索引可以用来让我们根据索引查询数据和以及确定唯一性的约束
openRequest.onupgradeneeded = function(e) {
console.log('数据库更新监听')
var thisDB = e.target.result
if (!thisDB.objectStoreNames.contains('people')) {
// 创建了一个名为 people 的对象存储,并假定每条数据都包含一个名为 email 的唯一属性
var peopleOS = thisDB.createObjectStore('people', {keyPath: 'email'})
// 在性别属性上建立索引,让你可以根据人的性别获取数据
peopleOS.createIndex('gender', 'gender', {unique: false})
// 在用户编号属性上建立索引,并且保证其是唯一
peopleOS.createIndex('ssn', 'ssn', {unique: true})
}
if (!thisDB.objectStoreNames.contains('notes')) {
// 创建了一个名为 notes 的对象存储,并使用一个自增值自动为主键赋值
var notesOS = thisDB.createObjectStore('notes', {autoIncrement: true})
notesOS.createIndex('title', 'title', {unique: false})
}
if (!thisDB.objectStoreNames.contains('logs')) {
// 创建了一个名为 logs 的对象存储。这一次,使用自增值并将其存储在一个名为 id的属性中
thisDB.createObjectStore('logs', {autoIncrement: true, keyPath: 'id'})
}
}
使用indexedDB操作数据
添加数据
function addPerson() {
var name = $('#name').val()
var email = $('#email').val()
console.log('添加' + name + '/' + email)
// 获取事物
var trancation = db.transaction(['people'], 'readwrite')
// 丛事物中获取存储对象
var store = trancation.objectStore('people')
var person = {
email: email,
name: name,
create_time: new Date().getTime()
}
var request = store.add(person)
request.onerror = function(e) {
console.log("Error",e.target.error.name);
}
request.onsuccess = function(e) {
console.log("成功添加" + name);
}
}
获取数据
// 根据主键获取用户
function getPerson () {
var key = $('#getmail').val()
if (key === '') return
// 通过数据库连接对象获取一个只读事务
var transcation = db.transaction(['people'], 'readonly')
// 获取到people对象存储
var store = transcation.objectStore('people')
// 通过主键获取对象信息
var request = store.get(key)
request.onsuccess = function(e) {
var result = e.target.result
console.log(result)
}
request.onerror = function(e) {
console.log('Error')
console.log(e)
}
}
更新数据
// 更新用户
function updatePerson () {
var email = $('#email2').val()
var name = $('#name2').val()
var transcation = db.transaction(['people'], 'readwrite')
var store = transcation.objectStore('people')
var person = {
name: name,
email: email,
create_at: new Date().getTime()
}
var request = store.put(person)
request.onsuccess = function (e) {
console.log('成功更新')
console.log(JSON.stringify(person))
}
request.onerror = function (e) {
console.log('Error')
console.log(e)
}
删除数据
function deletePerson () {
var key = $('#delete').val()
if (key === '') return
var transcation = db.transaction('people', 'readwrite')
var store = transcation.objectStore('people')
// 根据主键删除
var request = store.delete(key)
request.onsuccess = function(e) {
console.log("Person deleted");
console.dir(e);
}
request.onerror = function(e) {
console.log("Error");
console.dir(e);
}
}
获取全部
function getAllPeople () {
var transaction = db.transaction(["people"], "readonly")
var people = transaction.objectStore("people")
var cursor = people.openCursor()
cursor.onsuccess = function (e) {
var cursor = e.target.result
if (cursor) {
console.log(cursor)
cursor.continue()
}
}
transaction.oncomplete = function() {
console.log('读取全部完成')
}
}
完整代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<script src="https://cdn.bootcss.com/jquery/3.0.0/jquery.min.js"></script>
</head>
<body>
<h2>Add Person</h2>
<input type="text" id="name" placeholder="Name"><br/>
<input type="email" id="email" placeholder="Email"><br/>
<button id="addPerson">Add Person</button>
<h2>Add Note</h2>
<textarea id="note"></textarea>
<button id="addNote">Add Note</button>
<hr />
<p>
<input type="text" id="getmail" value="" />
<button id="getPerson">获取用户</button>
</p>
<p>
<input type="text" id="getnote" value="" />
<button id="getNote">获取备注</button>
</p>
<hr />
<input type="text" id="name2" placeholder="Name"><br/>
<input type="email" id="email2" placeholder="Email"><br/>
<button id="updatePerson">更新用户</button>
<hr />
<p>
<input type="text" id="delete" value="" />
<button id="deletePerson">删除用户</button>
</p>
<hr />
<button id="getAllPeople">获取全部用户</button>
</body>
<script>
function isIndexDBSupport() {
return 'indexedDB' in window && !/iPad|iPhone|iPod/.test(navigator.platform)
}
var db = null;
$(function() {
// 检查浏览器是否支持 IndexedDB
if (!isIndexDBSupport()) return;
// 用 indexedDB.open 方法打开数据库,并指明版本号(从1开始)
var openRequest = indexedDB.open('db3', 2)
openRequest.onupgradeneeded = function(e) {
console.log('数据库更新监听')
var thisDB = e.target.result
if (!thisDB.objectStoreNames.contains('people')) {
// 创建了一个名为 people 的对象存储,并假定每条数据都包含一个名为 email 的唯一属性
var peopleOS = thisDB.createObjectStore('people', {keyPath: 'email'})
}
if (!thisDB.objectStoreNames.contains('notes')) {
// 创建了一个名为 notes 的对象存储,并使用一个自增值自动为主键赋值
var notesOS = thisDB.createObjectStore('notes', {autoIncrement: true})
}
}
openRequest.onsuccess = function(e) {
console.log('数据库连接成功')
// 获取到数据库连接对象
db = e.target.result
console.log(db.objectStoreNames)
$('#addPerson').on('click', addPerson)
$('#addNote').on('click', addNote)
$('#getPerson').on('click', getPerson)
$('#getNote').on('click', getNotes)
$('#updatePerson').on('click', updatePerson)
$('#deletePerson').on('click', deletePerson)
$('#getAllPeople').on('click', getAllPeople)
}
openRequest.onerror = function(e) {
console.log('数据库连接异常')
console.dir(e)
}
})
// 添加用户
function addPerson () {
var name = $('#name').val()
var email = $('#email').val()
// 获取事物
var trancation = db.transaction('people', 'readwrite')
// 丛事物中获取存储对象
var store = trancation.objectStore('people')
var person = {
email: email,
name: name,
create_time: new Date().getTime()
}
var request = store.add(person)
request.onerror = function(e) {
console.log("Error",e.target.error.name);
}
request.onsuccess = function(e) {
console.log("成功添加" + name);
}
}
// 添加备注
function addNote () {
var note = $('#note').val()
console.log('添加' + note)
// 获取事物
var trancation = db.transaction(['notes'], 'readwrite')
// 从事务请求对象存储
var store = trancation.objectStore('notes')
var note = {
content: note,
create_time: new Date().getTime()
}
var request = store.add(note)
request.onerror = function(e) {
console.log("Error",e.target.error.name);
}
request.onsuccess = function(e) {
console.log("成功添加" + note);
}
}
// 根据主键获取用户
function getPerson () {
var key = $('#getmail').val()
if (key === '') return
var transcation = db.transaction(['people'], 'readonly')
var store = transcation.objectStore('people')
var request = store.get(key)
request.onsuccess = function(e) {
var result = e.target.result
console.log(result)
// 将值设置到更新输入框中
if (result) {
$('#email2').val(result.email)
$('#name2').val(result.name)
} else {
$('#email2').val('')
$('#name2').val('')
}
}
request.onerror = function(e) {
console.log('Error')
console.log(e)
}
}
function getNotes () {
var key = $('#getnote').val()
if (key === '') return
var transcation = db.transaction(['notes'], 'readonly')
var store = transcation.objectStore('notes')
var request = store.get(Number(key))
request.onsuccess = function (e) {
console.log(e.target.result)
}
request.onerror = function (e) {
console.log('Error')
console.log(e)
}
}
// 更新用户
function updatePerson () {
var email = $('#email2').val()
var name = $('#name2').val()
var transcation = db.transaction(['people'], 'readwrite')
var store = transcation.objectStore('people')
var person = {
name: name,
email: email,
create_at: new Date().getTime()
}
var request = store.put(person)
request.onsuccess = function (e) {
console.log('成功更新')
console.log(JSON.stringify(person))
}
request.onerror = function (e) {
console.log('Error')
console.log(e)
}
}
function deletePerson () {
var key = $('#delete').val()
if (key === '') return
var transcation = db.transaction('people', 'readwrite')
var store = transcation.objectStore('people')
// 根据主键删除
var request = store.delete(key)
request.onsuccess = function(e) {
console.log("Person deleted");
console.dir(e);
}
request.onerror = function(e) {
console.log("Error");
console.dir(e);
}
}
function getAllPeople () {
var transaction = db.transaction(["people"], "readonly")
var people = transaction.objectStore("people")
var cursor = people.openCursor()
cursor.onsuccess = function (e) {
var cursor = e.target.result
if (cursor) {
console.log(cursor)
cursor.continue()
}
}
transaction.oncomplete = function() {
console.log('读取全部完成')
}
}
</script>
</html>
优秀的第三方库
Locker 简化LocalStorge操作的库
Dexie简化IndexedDB
localForage 封装了多种客户端存储的库,支持IndexedDB、Web SQL 和本地存储
其他各种存储库wiki列表