【图文】Web前端之客户端存储——indexedDB数据库常见使用方法,超详细!看这一篇就够了

写在前面

该篇文章是博主在学习《JavaScript高级程序设计(第三版)》时归纳整理的,这篇文章里面用到的示例源代码均在我的github项目中,感兴趣的朋友可以点击这里下载源码来看,源码里面包括几乎所有随书所做的小demo,而且写了很多注释,相信是很容易看懂的。项目仍在更新中。

indexedDB数据库简介

indexedDB是浏览器中保存结构化数据的一种数据库,可以方便的读取和保存JavaScript对象。同时还支持查询和搜索功能。
该数据库最大的特点在于,所有的操作都是异步进行的,这意味着几乎每一步操作都有一个相应的事件处理程序,如onerror,onsuccess等。

1.创建/打开数据库

使用indexed的第一步是打开它,由于不同的浏览器实现差异,indexDB对象的获取可能会有不同

// 解决浏览器之间的差异
var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB

// 调用open方法打开数据库,若数据库不存在则创建数据库
// 第一个参数是要打开或创建的数据库的名称,第二个参数是数据库的版本号
var request = indexedDB.open("administer", 1)  

open方法会返回一个IDBRequest对象,用来处理该过程。可以为该对象设置事件处理函数,相关的操作完成后,IDBRequest对象的对应的属性会自动填充,从而触发所设置的事件。
通常我们需要设置以下三个事件处理函数:success,error,upgradeneeded

//操作成功的回调函数
request.onsuccess = function(event){
	// event.target就是request对象,可以用event.target.result获得创建的数据库
	console.log("indexedDB数据库打开/创建成功!")
}

//操作失败的回调函数
request.onerror = function(event){
	console.error("数据库创建/打开失败")
}

//数据库版本更新时的回调函数
request.onupgradeneeded = function(event){
	// 获取数据库
	var db = event.target.result
	// 在数据库版本更新时创建表(对象存储空间)
	db.createObjectStore("users", {keyPath: "id",autoIncrement: true})
}

需要注意的是,若数据库不存在,则创建数据库时会触发版本更新事件。因为创建数据库从无到有,设置了版本号,所以会触发。
另外,数据库中对象存储空间的创建只能在onupgradeneeded事件处理程序中设置。

indexedDB是非关系型数据库,存储的是对象。而关系型数据库存储的是表格,是一种关系。为了便于理解,大家可以将对象存储空间类比为关系数据库中的表格,而把其中存储的对象类比为表中的记录

可以在控制台的application下看到,administrator数据库创建成功:
【图文】Web前端之客户端存储——indexedDB数据库常见使用方法,超详细!看这一篇就够了_第1张图片

2.创建对象存储空间(表)

如果数据库的版本与你传入open函数的版本不匹配,那可能就需要创建一个新的对象存储存储空间。不过在此之前,你最好想清楚你想存放的是那种数据类型,并且为表格设置一个键用访问数据,这类似于关系数据库中表格的主键。
假如你需要存储的对象是这样的:

var user = {
	username: "李白",
	age: 25
}

那你可以像下面这样创建表:

//数据库版本更新时的回调函数
request.onupgradeneeded = function(event){
	// 获取数据库
	var db = event.target.result
	// 在数据库版本更新时创建表(对象存储空间)
	db.createObjectStore("users", {
		keyPath: "id",
		autoIncrement: true
	})
}

db.createObjectStore()方法接收两个参数:表名和一个配置对象。配置对象中的keyPath属性将作为存储空间的键来使用,可以指定autoIncrement属性为true,这样键值将会随着插入数据而自动增长,当然也可以不设置该属性。如果不设置自增属性的话就需要指定一个存储对象已有的属性来作为键,例如:

db.createObjectStore("users", {keyPath: "username"})

创建表格时可以为表格初始化一些数据,在创建时初始化可以不用使用事务(后面会提到)来完成。数据库上调用add()或put()即可添加对象。如下:

request.onupgradeneeded = function(event){
	var db = event.target.result
	var store = db.createObjectStore("users", {
		keyPath: "id",
		autoIncrement: true
	})
	
	// 为表格初始化数据
	var req = store.add(user)
	req.onerror = function(){
		//处理错误
	}
	req.onsuccess = function(){
		console.log("数据添加成功!")
	}
}

同样的,调用add()或put()也会返回一个请求对象,如果想验证请求是否处理完成,可以添加事件处理程序。

这里需要注意,add()和put()方法还是有一些区别的,两者的默认操作都是向表中添加数据,但是put()在添加时,如果添加的元素的已存在则会重写原有数据,这主要是通过来判断的,使用put()除了执行添加还可以执行更新。

打开application选项卡可以看到,users表创建成功:
表格创建成功

3.事务操作

数据库创建阶段可以初始化数据库而不用使用事务操作,除此之外,都必须使用事务操作来完成对数据的读写。事务可以像下面这样在数据库上调用方法:

db.transaction("users", "readwrite")

第一个参数为要打开的表名。可以同时打开多张表,传入一个表名数组即可
第二个参数为一个字符串,只读——“readonly”,读写——"readwrite"

我们可以指定一个按钮,用来完成添加和展示数据的功能:
html界面
相关html代码如下:

添加数据按钮的js代码如下:

// 获取表单元素
var indexForm = document.forms["indexedDB"]

//添加数据按钮事件处理程序
indexForm.add.onclick = function(event){
	var req = indexedDB.open("administer"), //打开数据库并构建数据
		user = {
			username: indexForm.username.value,
			age: indexForm.age.value
		}
	
	// 如果值为空则什么也不做
	if(user.username == "" && user.age == ""){
		return
	}
	
	//数据库打开成功的回调函数
	req.onsuccess = function(event){
		var db = event.target.result  // 获得数据库
		
		//创建事务
		var transaction = db.transaction("users", "readwrite")
		req = transaction.objectStore("users").add(user)
		
		req.onsuccess = function(event){
			console.log("添加成功!")
		}
		req.onerror = function(event){
			console.error("数据插入失败")
		}
		
		transaction.onerror = function(){
			console.error("事务未执行完成")
		}
		transaction.oncomplete = function(){
			console.info("事务完成!")
		}
	}
}

可以看到,创建事务依然返回请求对象并且是异步执行的,按照前面的惯例,这里设置onerror事件和oncomplete事件,前者代表事务执行失败,数据的操作全部撤销,后者代表事务中所有操作都成功执行完毕。

另外在事务对象中调用objectStore(“users”)方法即可获得已存在的表对象,即上文中用到的store变量,然后执行add()等操作都是轻而易举啦!

这里添加了几条数据,同样的,打开application查看:
插入的数据

4.游标查询

游标查询中的游标实际上就是一个遍历器,可以把它看作一个iterator。
使用事务可以通过已知的键来检索单个对象,但是如果需要检索的对象很多,例如查询全部数据的操作,则需要在事务内部创建游标

在对象存储空间(表)上调用openCursor()方法可以创建游标。与indexedDB中其他操作一样,该操作会返回一个请求对象,因此必须为对象指定onsuccess、onerror事件处理程序。

在onsuccess事件处理程序内部:通过event.target.result可以取得存储空间的下一项,在没有下一项时会返回null,必须要在使用游标之前检查该属性

游标对象IDBCursor有四个属性

  • direction:数值,表示游标移动的方向:IDBCursor.NEXT表示下一项,IDBCursor.NEXT_NO_DUPLICAT表示下一个不重复的项目,IDBCursor.PREV表示前一项,IDBCursor.PREV_NO_DUPLICAT表示前一个不重复的项
  • key:对象的键
  • value:实际的对象
  • primaryKey:游标使用的键

游标创建可以传入参数,第一个参数是键范围,第二个是游标方向:

4.1 键范围——IDBKeyRange

该对象不同浏览器实现存在差异,可像编写跨浏览器兼容方法。

var IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange

有四种定义键范围的方式:

  1. 使用only()方法,并传入你想要取得的对象的键
    var range = IDBKeyRange.only("1")
  2. 指定结果集的下界
    var range = IDBKeyRange.lowerBound("15", true) //如果你想忽略当前值,从一个值开始,可以传入第二个可选参数,并设置为true。类似于集合两端的开和闭
  3. 指定结果集的上界
    var range = IDBKeyRange.upperBound("1", true)
  4. 同时指定上下界
    var range = IDBKeyRange.bound("1", "3", true, true) // 后两个参数可选,同理,类似于集合两端的开闭规则
4.2 游标方向

游标方向即为IDBCursor对象实例的四个属性,上方提到过,只需在创建有标识传入第二个参数即可,例如:

var req = store.openCursor(null, IDBCursor.PREV) // 该游标将从后向前迭代

4.3 实例解析——游标的跳转

下面让我们一起来为表单中查询数据按钮添加功能:

//查询数据按钮
indexForm.display.onclick = function(event){
	var req = indexedDB.open("administer")
		
	req.onsuccess = function(event){
		var db = event.target.result
		var transaction = db.transaction("users", "readonly")
		
		// 游标遍历数据,创建游标
		// 创建游标可传入参数,第一个参数为键范围,第二个参数为游标方向
		// 也可以不传参数
		var store = transaction.objectStore("users"),
		cursorReq = store.openCursor()
			
		cursorReq.onsuccess = function(event){
			var cursor = event.target.result //IDBCursor实例
			if (cursor) { //如果没有下一项则为null
				//注意cursor.value是一个对象,所以要将其转换成字符串
				console.log("Key:"+cursor.key+","+"Value:"+JSON.stringify(cursor.value))
				cursor.continue() //不指定参数则跳转至下一项
			}
		}
		cursorReq.onerror = function(event){
			console.error("查询失败")
		}
		//事务事件处理程序
		transaction.onerror = function(){
			console.error("事务未执行完成")
		}
		transaction.oncomplete = function(){
			console.info("事务完成!")
		}
	}
}

在以上代码中我们看到了如下代码:

cursorReq.onsuccess = function(event){
	var cursor = event.target.result //IDBCursor实例
	if (cursor) { //如果没有下一项则为null
		//注意cursor.value是一个对象,所以要将其转换成字符串
		console.log("Key:"+cursor.key+","+"Value:"+JSON.stringify(cursor.value))
		cursor.continue() //不指定参数则跳转至下一项
	}
}

我们注意到:cursor.continue()方法,调用该方法会使得游标按设定的方向移动到下一项,该方法可以传入一个可选参数,即要跳转到的对象的键值。不指定参数则默认按照预设方向跳转到下一项

另外,cursor对象还有一个方法:cursor.advance(count)——按照预设方向,向前移动count指定的项数

这两个方法都会使得游标使用相同的请求,即相同的onsuccess和onerror事件处理程序会得到复用。因此可以反复迭代,最终实现需求。

最后,点击查询按钮即可在控制台看到效果

遍历表

5. 补充和限制:

以下内容摘自《JavaScript高级程序设计(第三版)》653页

虽然网页中的indexedDB提供的时一套异步接口,但是仍然存在并发问题,若浏览器的两个不同的标签打开了同一个页面,那么一个页面视图更新另一个页面尚未就绪的数据库的问题就有可能发生。
刚打开数据库时,记得指定onversionchange事件,当同源页面调用setVersion()时就会触发该事件。处理这个事件的最佳方式时立即关闭数据库从而保证版本更行顺利完成。在onversionchange中调用db.close()方法即可。
调用setVersion()时指定请求的onblocked事件处理程序也很重要,在你想更新数据库版本但其他标签页已经打开数据库的情况下,就会触发这个时间,因此最好提醒用户关闭其他页面,然后重新调用setVersion()。
indexedDB只能由同源页面操作,不支持跨域共享信息。其次,每个来源的数据库占用的磁盘空间也有限制。

其实还有一项内容——索引,但是考虑到,并不常使用,这里不做介绍(其实是因为博主太懒哈哈哈啊哈),感兴趣的朋友可以自己去找资料看看呀。如果这篇文章对你有帮助的话,记得帮我点个赞呀。

由于博主水平有限,该文章如果存在错误,欢迎各位大牛评论,批评指正!

你可能感兴趣的:(Web前端)