各种存储方式差异与局限性
localStorage/sessionStorage的优势
- localStorage拓展了cookie的4K限制
- localStorage会可以将第一次请求的数据直接存储到本地,这个相当于一个5M大小的针对于前端页面的数据库,相比于cookie可以节约带宽,但是这个却是只有在高版本的浏览器中才支持的
localStorage/sessionStorage的局限
- 浏览器的大小不统一,并且在IE8以上的IE版本才支持localStorage这个属性
- 目前所有的浏览器中都会把localStorage的值类型限定为string类型,这个在对我们日常比较常见的JSON对象类型需要一些转换
- localStorage在浏览器的隐私模式下面是不可读取的
- localStorage本质上是对字符串的读取,如果存储内容多的话会消耗内存空间,会导致页面变卡
- localStorage不能被爬虫抓取到
localStorage与sessionStorage的唯一一点区别就是localStorage属于永久性存储,而sessionStorage属于当会话结束的时候,sessionStorage中的键值对会被清空
IndexedDB 特点
- IndexedDB 数据库使用key-value键值对储存数据. values 数据可以是结构非常复杂的对象,key可以使对象自身的属性。你可以对对象的某个属性创建索引(index)以实现快速查询和列举排序。.key可以使二进制对象
- IndexedDB 是事务模式的数据库. 任何操作都发生在事务(transaction)中。 IndexedDB API提供了索引(indexes), 表(tables), 指针(cursors)等等, 但是所有这些必须是依赖于某种事务的。因此,你不能在事务外执行命令或者打开指针。事务(transaction)有生存周期, 在生存周期以后使用它会报错。并且,事务(transaction)是自动提交的,不可以手动提交。
- The IndexedDB API 基本上是异步的. IndexedDB的API不通过return语句返回数据,而是需要你提供一个回调函数来接受数据。执行API时,你不以同步(synchronous)方式对数据库进行“存储”和“读取”操作,而是向数据库发送一个操作“请求”。当操作完成时,数据库会以DOM事件的方式通知你,同时事件的类型会告诉你这个操作是否成功完成。这个过程听起来会有些复杂,但是里面是有明智的原因的。这个和XMLHttpRequest请求是类似的
- IndexedDB数据库“请求”无处不在 我们上边提到,数据库“请求”负责接受成功或失败的DOM事件。每一个“请求”都包含onsuccess和onerror事件属性,同时你还对“事件”调用addEventListener()和removeEventListener()。“请求”还包括readyState,result和errorCode属性,用来表示“请求”的状态。result属性尤其神奇,他可以根据“请求”生成的方式变成不同的东西,例如:IDBCursor实例、刚插入数据库的数值对应的键值(key)等。
- IndexedDB在结果准备好之后通过DOM事件通知用户 DOM事件总是有一个类型(type)属性(在IndexedDB中,该属性通常设置为success或error)。DOM事件还有一个目标(target)属性,用来告诉事件是被谁触发的。通常情况下,目标(target)属性是数据库操作生成的IDBRequest。成功(success)事件不弹出提示并且不能撤销,错误(error)事件会弹出提示且可以撤销。这一点是非常重要的,因为除非错误事件被撤销,否则他们会终止所在的任何事务。
- IndexedDB是面向对象的。indexedDB不是用二维表来表示集合的关系型数据库。传统的关系型数据库,你需要用到二维表来存储数据集合(每一行代表一个数据,每一列代表一个属性),indexedDB有所不同,它要求你为一种数据创建一个对象存储(object Store),只要这种数据一个JavaScript对象即可。每个对象存储都有一个索引(index)集合以方便查询和迭代遍历。如果你不熟悉面向对象的数据库管理系统,可以参考维基百科有关对象数据库的内容
- indexedDB不使用结构化查询语言(SQL)。它通过索引(index)所产生的指针(cursor)来完成查询操作,从而使你可以迭代遍历到结果集合。如果你不熟悉NoSQL系统,可以参考维基百科相关文章。
- IndexedDB遵循同源(same-origin)策略 “源”指脚本所在文档URL的域名、应用层协议和端口。每一个“源”都有与其相关联的数据库。在同一个“源”内的所有数据库都有唯一、可区别的名称。
IndexedDB vs Web Storage
Web Storage使用简单字符串键值对在本地存储数据,方便灵活,但是对于大量结构化数据存储力不从心,IndexedDB是为了能够在客户端存储大量的结构化数据,并且使用索引高效检索的API。
IndexedDB的技术特点是,不需要你去写特定的sql语句来对数据进行操作,它是nosql的,数据形式使用的是json。
IndexedDB里数据以对象的形式存储,每个对象都有一个key值索引。IndexedDB里的操作都是事务性的。一种对象存储在一个objectStore里,objectStore就相当于关系数据库里的表。IndexedDB可以有很多objectStore,objectStore里可以有很多对象。每个对象可以用key值获取。
IndexedDB vs LocalStorage
IndexedDB和LocalStorage都是用来在浏览器里存储数据,但它们使用不同的技术,有不同的用途,你需要根据自己的情况适当的选择使用哪种。LocalStorage是用key-value键值模式存储数据,但跟IndexedDB不一样的是,它的数据并不是按对象形式存储。它存储的数据都是字符串形式。如果你想让LocalStorage存储对象,你需要借助JSON.stringify()能将对象变成字符串形式,再用JSON.parse()将字符串还原成对象。但如果要存储大量的复杂的数据,这并不是一种很好的方案。毕竟,localstorage就是专门为小数量数据设计的,它的api是同步的。
IndexedDB很适合存储大量数据,它的API是异步调用的。IndexedDB使用索引存储数据,各种数据库操作放在事务中执行。IndexedDB甚至还支持简单的数据类型。IndexedDB比localstorage强大得多,但它的API也相对复杂。
对于简单的数据,你应该继续使用localstorage,但当你希望存储大量数据时,IndexedDB会明显的更适合,IndexedDB能提供你更为复杂的查询数据的方式。
IndexedDB vs Web SQL
WebSQL也是一种在浏览器里存储数据的技术,跟IndexedDB不同的是,IndexedDB更像是一个NoSQL数据库,而WebSQL更像是关系型数据库,使用SQL查询数据。W3C已经不再支持这种技术。
因为不再支持,所以你就不要在项目中使用这种技术了。
IndexedDB vs Cookies
Cookies,每次HTTP接受和发送都会传递Cookies数据,它会占用额外的流量。例如,如果你有一个10KB的Cookies数据,发送10次请求,那么,总计就会有100KB的数据在网络上传输。Cookies只能是字符串。浏览器里存储Cookies的空间有限,很多用户禁止浏览器使用Cookies。所以,Cookies只能用来存储小量的非关键的数据。
基本操作
localStorage/sessionStorage基本操作
(1)setItem(key,value):添加本地存储数据。两个参数,非常简单就不说了。
(2)getItem(key):通过key获取相应的Value。
(3)removeItem(key):通过key删除本地数据。
(4)clear():清空数据。
IndexedDB 基本操作
创建、打开数据库
需要根据不同浏览器的内核,创建indexedDB对象var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB
打开一个数据库,open方法, 该方法接收两个参数:
- dbName // 数据库名称 [string]
- version // 数据库版本 [整型number]
var name = 'person-text', version = 1, db
var request = indexedDB.open(name, version)
request.onsuccess = function(event) { db = event.target.result; db.onsuccess = function(event) { console.log('数据库操作成功!') }; db.onerror = function(event) { console.error('数据库操作发生错误!', event.target.errorCode) }; console.log('打开数据库成功!') } request.onerror = function(event) { console.error('创建数据库出错') console.error('error code:', event.target.errorCode) } request.onupgradeneeded = function(event) { // 更新对象存储空间和索引 .... }
若是本域下不存在名为 person-text 的数据库,则上述代码会创建一个名为person-text、版本号为1的数据库; 触发的事件依次为: upgradeneeded、 success.
若是已存在名为person-text的数据库, 则上述代码会打开该数据库; 只触发success/error事件,不会触发upgradeneeded事件. db是对该数据库的引用.
创建对象存储空间和索引
在key-value型数据库(如indexedDB)中, 一个数据库会有多个对象存储空间,每个存储空间有自己的主键、索引等;
创建对象存储空间的操作一般放在创建数据库成功回调里:
request.onupgradeneeded = function(event) {
// 更新对象存储空间和索引 ....
var database = event.target.result var objectStore = database.createObjectStore("person", { keyPath: "id" }) objectStore.createIndex('name', 'name', { unique: true }) objectStore.createIndex('age', 'age', { unique: false }) }
onupgradeneeded 是我们唯一可以修改数据库结构的地方。在这里面,我们可以创建和删除对象存储空间以及构建和删除索引。
在数据库对象database上,有以下方法可供调用:
- createObjectStore(storeName, configObj) 创建一个对象存储空间
- storeName // 对象存储空间的名称 [string]
- configObj // 该对象存储空间的配置 [object] (其中的keyPath属性值,标志对象的该属性值唯一)
- createIndex(indexName, objAttr, configObj) 创建一个索引
- indexName // 索引名称 [string]
- objAttr // 对象的属性名 [string]
- configObj // 该索引的配置对象 [object]
数据操作(增删改查)
对数据库的操作(增删查改等)都需要通过事务来完成,事务具有三种模式:
- readonly 只读(可以并发进行,优先使用)
- readwrite 读写
- versionchange 版本变更
数据库对象的transaction()方法接收两个参数:
- storeNames // 对象存储空间,可以是对象存储空间名称的数组,也可以是单个对象存储空间名称,必传 [array|string]
- mode // 事务模式,上面提到的三种之一,可选,默认值是readonly [string]
var transaction = db.transaction(['person'], 'readwrite')
transaction.oncomplete = function(event) { console.log('事务完成!') } transaction.onerror = function(event) { console.log('事务失败!', event.target.errorCode) } transaction.onabort = function(event) { console.log('事务回滚!') }
向数据库中增加数据
通过事务对象transaction,在objectStore()方法中指定对象存储空间,就得到了可以对该对象存储空间进行操作的对象objectStore.
向数据库中增加数据,add()方法增加的对象,若是数据库中已存在相同的主键,或者唯一性索引的键值重复,则该条数据不会插入进去
var objectStore = transaction.objectStore('person') // 指定对象存储空间
var data = [{"name": "张三", "age": "21", "sex": "男", "id": "11" }, {"name": "李四", "age": "19", "sex": "男", "id": "12" }, {"name": "王五", "age": "21", "sex": "女", "id": "13"}, {"name": "赵六", "age": "24", "sex": "男", "id": "14"}] data.forEach(function(item, index){ var request = objectStore.add(item) request.onsuccess = function(event) { console.log('插入成功!', index) console.log(event.target.result, item.id); // add()方法调用成功后result是被添加的值的键(id) } })
修改数据库中的数据
var objectStore = transaction.objectStore('person')
objectStore.put({"name": "李四", "age": "22", "sex": "男", "id": "12" })
该方法与add()不同之处在于,数据库中若存在相同主键或者唯一性索引重复,则会更新该条数据,否则插入新数据。
从数据库中删除数据
var objectStore = transaction.objectStore('person')
var request = objectStore.delete('12'); // 通过键id来删除 request.onsuccess = function(event) { console.log('删除成功!'); }
从数据中获取数据
var objectStore = transaction.objectStore('person')
var request = objectStore.get('13')
request.onsuccess = function(event) { console.log('获取成功!', event) }
使用索引
在前面,我们创建了两个索引name和age, 配置对象里面的unique属性标志该值是否唯一
现在我们想找到name属性值为'张三'的对象,就可以使用索引。
var objectStore = db.transaction('person').objectStore('person') // 打开对象存储空间
var index = objectStore.index('name') // 使用索引 var request = index.get('张三') // 创建一个查找数据的请求 request.onsuccess = function(event) { console.log('The result is:', event.target.result) }
使用游标
使用一次索引,我们只能得到一条数据; 如果我们需要得到所有数据,或某一类数据,可以使用游标.
得到一个可以操作游标的请求对象有两个方法:
- openCursor(keyRange, direction)
- openKeyCursor(keyRange, direction)
这两个方法接收的参数一样, 两个参数都是可选的: 第一个参数是限制值得范围,第二个参数是指定游标方向
keyRange是限定游标遍历的数据范围,通过IDBKeyRange的一些方法设置该值:
// 匹配值为Bill的数据
var lowerBoundKeyRange = IDBKeyRange.only("Bill")
// 匹配所有在 "Bill" 前面的, 包括 "Bill"
var lowerBoundKeyRange = IDBKeyRange.lowerBound("Bill"); // 匹配所有在 “Bill” 前面的, 但是不需要包括 "Bill" var lowerBoundOpenKeyRange = IDBKeyRange.lowerBound("Bill", true); // 匹配所有在'Donna'后面的, 但是不包括"Donna" var upperBoundOpenKeyRange = IDBKeyRange.upperBound("Donna", true); // 匹配所有在"Bill" 和 "Donna" 之间的, 但是不包括 "Donna" var boundKeyRange = IDBKeyRange.bound("Bill", "Donna", false, true);
游标默认遍历方向是按主键从小到大,有时候我们倒序遍历,此时可以给openCursor()
方法传递第二个参数: direction: next|nextunique|prev|prevunique
游标的使用有以下几处:
- 在对象存储空间上使用: var cursor = objectStore.openCursor()
- 在索引对象上使用: var cursor = index.openCursor()
var list = [];
var index = db.transaction('person').objectStore('person')
index.openCursor().onsuccess = function(event) { var cursor = event.target.result if (cursor) { console.log('cursor.value:', cursor.value); list.push(cursor.value) cursor.continue() } else { console.log('list:', list) } }
var list = []
var index = db.transaction('person').objectStore('person').index('age') index.openCursor('24').onsuccess = function(event) { var cursor = event.target.result if (cursor) { console.log('cursor.value:', cursor.value); list.push(cursor.value) cursor.continue() } else { console.log('list:', list) } }
使用游标时,需要在成功回调里拿到result对象,判断是否取完了数据:若数据已取完,result是undefined; 若未取完,则result是个IDBCursorWithValue对象,需调用continue()方法继续取数据。 也可以根据自己需求, 对数据进行过滤。
在索引age上使用openCursor()方法时,若不传参数,则会遍历所有数据
关闭和删除数据库
关闭数据库只需要在数据库对象db上调用close()方法即可 db.close()
关闭数据库后,db对象仍然保存着该数据库的相关信息,只是无法再开启事务
删除数据库则需要使用indexedDB.deleteDatabase(dbName)
方法