《JavaScript高级程序设计》离线应用与客户端存储(学习笔记C23)

本文探讨了离线应用的检测方法,包括navigator.onLine属性和online/offline事件,以及HTML5应用缓存机制。深入讲解了Cookie的使用、限制和子Cookie的概念。介绍了IndexedDB数据库的使用,包括数据库操作、对象存储空间、事务处理、游标查询、键范围设定、索引创建和并发问题。

离线检测

该小结重点:一个属性(navigator.online)两个事件(online/offline)
navigator.onLine: true->设备能上网;false->设备离线。
navigator.onLine 在不同浏览器间有小差异

  1. IE6+ Safari5+ 能正确应用
  2. Firefox3+ Opera10.6+ 手动设置“文件->Web开发人员(设置)->脱机工作”
  3. Chrome11及之前 navigator.onLine 始终为true
	if (navigator.onLine) {
		// 正常工作
	} else {
		// 执行离线状态时的任务
	}

online: 当网络从离线变为在线时(在window对象上)触发该事件
offline: 当网络从在线变为离线时(在window对象上)触发该事件

	EventUtil.addHandler(window, 'online', function() {
		alert('Online')
	})
	EventUtil.addHandler(window, 'offline', function() {
		alert('Offline')
	})

用法:
先通过 navigator.onLine 取得初始状态,
然后通过 online,offline 事件确定网络连接状态是否变化,
最后事件触发时,navigator.online属性的值也会改变,不过必须手工轮询这个属性才能检测到网络状态的变化

应用缓存

HTML5的应用缓存(appplication cache)简称appcache,是专门为开发离线Web应用设计的
Appcache是从浏览器缓存中分出来的一块缓存区
要想在这个缓存区保存数据可以使用一个描述文件(manifest file),列出要下载和缓存的资源
将描述文件和页面关联起来: 在manifest属性中指定这个文件路径
<html manifest="/offline.manifest">
这个文件的MIME类型必须是 text/cache-manifest
描述文件的扩展名推荐使用已从 manifest 改为 appcache

离线API核心对象applicationCache对象的status属性和相关事件:
0: 无缓存
1: 闲置 即应用缓存未得到更新
2: 检查中 即正在下载描述文件并检查更新
3: 下载中 即应用缓存正在下载描述文件中指定资源
4: 更新完成 即资源已更新且下载完毕,可以通过swapCache()使用
5: 废弃 即应用缓存的描述文件已经不存在,页面无法再访问应用缓存

表示状态改变的事件:(会随着页面加载按顺序触发)
checking: 在浏览器为应用缓存查找更新时触发
error: 在检查更新或下载资源期间发生错误时触发
noupdate: 在检查描述文件发现文件无变化时触发
downloading: 在开始下载应用缓存的过程中触发
progress: 在文件下载应用缓存的过程中持续不断地触发
updateready: 在页面新的应用混啊村下载完毕且可以通过swapCache()使用时触发
cached: 在应用缓存完整时触发

applicationCache.update()

手动干预让应用缓存为检查更新触发上述事件

	EventUtil.addHandler(applicationCache, 'updateready', function(){
		applicationCache.swapCache()
	})
	// 在firefox4及之前版本调用swapCache()会抛出错误

数据存储

Cookie

1.可以作为响应的一部分
2.浏览器存储后为每个请求添加Cookies HTTP头将信息发回服务器
3. 用于唯一验证客户

1.限制

1.cookie在性质上是绑定在特定域名下的
2.每个域的cookie总数有限

  1. Firefox限制50个cookie每个域
  2. Opera限制30个
  3. Safari和Chrome没有硬性规定

3.浏览器对cookie的尺寸有限制,约4096B(一个域下所有cookie)

2.cookie的构成

名称、值、域、路径(可以指定cookie只有从xx域中才能访问)、失效时间、安全标志(指定后cookie只有使用SSL连接时才能发送,如https能http不能)
只有名值对会发送到服务器

	// 示例
	HTTP/1.1 200 OK
	Content-type: text/html
	Set-Cookie: name=value; expires=Mon, 22-Jan-07 GMT; domain=.wrox.com; path=/; secure
	Other-header: other-header-value

3.JavaScript中的cookie

1.document.cookie

  1. 获取属性值时,document.cookie返回当前页面可用的所有cookie的字符串,如
    name1=value1;name2=value2;name3=value3……
    所有名字和值都是经过URL编码的,所以必须使用decodeURIComponent()来解吗
  2. 设置属性值时,和Set-Cookie头中格式一样
    name=value; expires=expiration_time; path=domain_path; secure (只有名字和值是必须的)
    document.cookie = encodeURIComponent('name') + '=' + encodeURIComponent('Nicholas')
    追加更多信息时
    document.cookie = encodeURIComponent('name') + '=' + encodeURIComponent('Nicholas') + "; domain=.wrox.com; path=/";

2.cookie函数操作

  1. 读取
  2. 写入
  3. 删除
	var CookieUtil = {
		// 读取
		get: function (name) {
			var cookieName = encodeURIComponent(name) + '=',
				cookieStart = document.cookie.indexOf(cookieName),
				cookieValue = null
				
			if (cookieStart > -1) {
				var cookieEnd = document.cookie.indexOf(';', cookieStart)
				if (cookieEnd == -1) {
					cookieEnd = document.cookie.length
				}
				cookieValue = decodeURIComponent(document.cookie.substring(cookieStart + cookieName.length, cookieEnd))
			}
			return cookieValue
		},
		// 写入
		set: function (name, value, expires, path, domain, secure) {
			var cookieText = encodeURIComponent(name) + '=' +
							 encodeURIComponent(value)
			if (expires instanceof Date) {
				cookieText += ";expires=" + expires.toGMTString()
			}
			if (path) {
				cookieText += ";path=" + path
			}
			if (domain) {
				cookieText += ";domain=" + domain
			}
			if (secure) {
				cookieText += ";secure"
			}
			document.cookie = cookieText
		},
		// 删除
		unset: function (name, path, domain, secure) {
			this.set(name, "", new Date(0), path, domain, secure)
		}
		// 使用
		
		// 设置cookie
		CookieUtil.set('name', 'Nike')
		CookieUtil.set('book', 'Professional JavaScript')
		
		// 读取cookie的值
		alert(CookieUtil.get('name')) // 'Nike'
		alert(cookieUtil.get('book')) // 'Professional JavaScript'
		
		// 删除cookie
		CookieUtil.unset('name')
		cookieUtil.unset('book')
		
		// 设置cookie, 包括它的路径、域、失效日期
		CookieUtil.set('name', 'Nike', '/books/projs/', 'www.wrox.com', new Date('January 1, 2010'))
		
		// 删除刚刚设置的cookie
		CookieUtil.unset('name', '/books/projs/', 'www.wrox.com')
		
		// 设置安全的cookie
		CookieUtil.set('name', 'Nike', null, null, null, true)
	}

4.子cookie

	var SubCookieUtil = {
		get: function (name, subName){
			var subCookies = this.getAll(name)
			if (subCookies) {
				return subCookies(subName)
			} else {
				return null
			}
		},
		getAll: function (name) {
			var cookieName = encodeURIComponent(name) + '=',
				cookieStart = document.cookie.indexOf(cookieName),
				cookieValue = null,
				cookieEnd,
				subCookies,
				i,
				parts,
				result = {}
			if (cookieStart > -1) {
				cookieEnd = docoment.cookie.indexOf(';', cookieStart)
				if (cookieEnd == -1) {
					cookieEnd = document.cookie.length
				}
				cookieValue = document.cookie.substr(cookieStart + cookieName.length, cookieEnd)
			}
			if (cookieValue.length > 0) {
				subCookies = cookieValue.split('&')
				
				for(i=0, len=subCookies.length; i<len; i++){
					parts = subCookies[i].split('=')
					result[decodeURIComponent(parts[0])] = 
						   decodeURIComponent(parts[1])
				}
				
				return result
			}
		}
		
		return null
	}
	
	// 使用
	
	// 假设document.cookie=data=name=Nicholas&book=Professional%20JavaScript
	
	// 取得全部子cookie
	var data = SubCookieUtil.getAll('data')
	alert(data.name) // 'Nicholas'
	alert(data.book) // 'Professional JavaScript'
	
	// 逐个获取子cookie
	alert(SubCookieUtil.get('data', 'name')) // 'Nicholas'
	alert(SubCookieUtil.get('data', 'book')) // 'Professional JavaScript'
	
	// 要设置子cookie,也有两种方法:set()和setAll()。
	// 以下代码展示了它们的构造
	var SubCookieUtil = {
		// cookie名称 子cookie名称 子cookie值 可选的cookie失效日期或时间的Data对象 可选的cookie路径 可选的cookie域和可选的布尔secure标志
		set: function (name, subName, value, expires, path, domain, secure) {
			var subcookies = this.getAll(name) || {}
			subcookies[subName] = value
			this.setAll(name, subcookies, expires, path, domain, secure)
		}
		setAll: function (name, subcookies, expires, path, domain, secure) {
			var cookiesText = encodeURIComponent(name) + '=',
				subcookieParts = new Array(),
				subName;
			for (subName in subcookies) {
				if (subName.length > 0 && subcookies.hasOwnProperty(subName)) {
					subcookieParts.push(encodeURIComponent(subName) + '=' + 
										encodeURIComponent(subcookies[subName])) 
				}
			}
			if (cookieParts.length > 0) {
				cookieText += subcookieParts.join('&')
				
				if (expires instanceof Data) {
					cookieText += ";expires=" + expires.toGMTString()
				}
				
				if (path) {
					cookieText += ";path=" + path
				}
				
				if (domain) {
					cookieText += ";domain=" + domain
				}
				
				if (secure) {
					cookieText += ";secure"
				}
			} else {
				cookieText += ";expires=" + (new Date(0).toGMTString())
			}
			document.cookie = cookieText
		},
		// 省略了更多代码
	}
	
	// 使用
	
	// 假设document.cookie=data=name=Nicholas&book=Professional%20JavaScript
	
	// 设置两个cookie
	SubCookieUtil.set('data', 'name', 'Nicholas')
	SubCookieUtil.set('data', 'book', 'Professional JavaScript')
	
	// 设置全部子cookie和失效日期
	SubCookieUtil.setAll('data', { name: 'Nicholas', book: 'Professional JavaScript' }, new Date('January 1, 2010'))
	
	// 修改名字的值,并修改cookie的失效日期
	SubCookieUtil.set('data', 'name', 'Michael', new Date('February 1, 2010'))
	
	// 普通cookie可以通过设置失效时间为过去的时间来删除,但是子cookie不行
	// 删除子cookie必须先获取包含在某个cookie中的所有子cookie然后仅删除需要删除的子cookie最后将余下cookie保存
	
	var SubCookieUtil = {
		// 这里省略了很多代码
		// 删除某个子cookie
		unset: function(name, subName, path, domain, secure) {
			var subcookies = this.getAll(name)
			if (subCookies) {
				delete subcookies[subName]
				this.setAll(name, subcookies, null, path, domain, secure)
			}
		},
		// 删除整个cookie
		unsetAll: function (name, path, domain, secure) {
			this.setAll(name, null, new Date(0), path, domain, secure)
		}
	}
	
	// 使用
	// 仅删除名为name的子cookie
	SubCookieUtil.unset('data', 'name')
	// 删除整个cookie
	SubCookieUtil.unsetAll('data')

5.关于cookie的思考

1."HTTP专有cookie"只能从服务器读取
2.cookie会作为请求头发送,太大会影响性能
3.不要在cookie存储重要和敏感信息

IE用户数据

Web存储机制

Web Storage的主要两个目的:
1.提供一种在cookie之外存储会话数据的途径
2.提供一种存储大量可以跨会话存在的数据的机制
最初的Web Storage规范包含了两种对象的定义:sessionStorage 和 globalStorage

1.Storage类型

方法:
clear(): 删除所有值Firefox没有实现
getItem(name): 根据指定的名字name获取对应值
key(index): 获得index位置处的值的名字
removeItem(name): 删除由name指定的名值对儿
setItem(name, value): 为指定的name设置一个对应的值

2.sessionStorage对象

seeesionStorage其实是Storage的一个实例
该数据只保持到浏览器关闭,浏览器关闭后消失,但浏览器刷新后依旧存在
该数据只能最初给对象存储数据的页面访问到,跨页面有限制

	// 使用方法存储数据
	sessionStorage.setItem('name', 'Nicholas')
	
	// 使用属性存储数据
	sessionStorage.book = 'Professional JavaScript'
	
	// 使用方法读取数据
	var name = sessionStorage.getItem('name')
	
	// 使用属性读取数据
	var book = sessionStorage.book
	
	// 通过length属性和key()方法来迭代sessionStorage的值
	for (var i=0, len=sessionStorage.length; i<len; i++) {
		var key = sessionStorage.key(i)
		
		var value = sessionStorage.getItem(key)
		alert(key + '=' + alue)
	}
	
	// 或者用for-in来循环迭代sessionStorage的值
	for (var key in sessionStorage) {
		var value = sessionStorage.getItem(key)
		alert(key + '=' + value)
	}
	
	// 使用delete删除一个值--在wenkie中无效
	delete sessionStorage.name
	
	// 使用方法删除一个值
	sessionStorage.removeItem('book')

3.globalStorage对象

如果不使用removeItem()或者delete删除,或者用户未清除浏览器缓存,存储在globalStorage属性中的数据会一直保留在磁盘中

	// 可以指定哪些域可以访问
	
	// 保存数据
	globalStorage['wrox.com'].name = 'Nicholas'
	
	// 获取数据
	var name = globalStorage['wrox.com'].name
	
	// 存储数据,任何人都可以访问(不要这样做)
	globalStorage[''].name = 'Nicholas'
	
	// 存储数据们可以让任何以.net结尾的域名访问(不要这样做)
	globalStorage['net'].name = 'Nicholas'
	
	// globalStorage的每个属性都是Storage的实例
	globalStorage['www.wrox.com'].name = 'Nocholas'
	globalStorage['www.wrox.com'].book = 'Profession JavaScript'
	
	globalStorage['www.wrox.com'].removeItem('name')
	
	var book = globalStorage['www.wrox.com'].getItem('book')
	
	// 如果不确定域名,可以用location.host作为域名
	globalStorage[location.host].name = 'Nocholas'
	var book = globalStorage[location.host].getItem('book')

4.localStorage对象

要访问同一个localStorage对象,页面必须来自同一个域名(子域名无效),使用同一种协议,在同一个端口上。相当于globalStorage[location.host]

存储在localStorage中的数据和存储在globalStorage中的数据一样,都遵循相同规则:数据保留到JS删除或者用户清除浏览器缓存
不能指定任何访问规则

	// 使用方法存储数据
	localStorage.setItem('name', 'Nicholas')
	
	// 使用属性存储数据
	localStorage.book = 'Professional JavaScript'
	
	// 使用方法读取数据
	var name = localStorage.getItem('name')
	
	// 使用属性读取数据
	var book = localStorage.book
	
	// 兼容只支持globalStorage的浏览器
	function getLocalStorage() {
		if (typeof localStorage == 'object' ) {
			return localStorage
		} else if(typeof globalStorage == 'object') {
			return globalStorage[location.host]
		} else {
			throw new Error('Local storage not available')
		}
	}
	var storage = getLocalStorage()

5.storage事件

无论对sessionStorage、globalStorage还是localStorage进行操作,都会触发该事件
该事件有以下属性:
domain: 发生变化的存储空间的域名
key: 设置或删除的键名
newValue: 如果是设置值,则是新值;如果是删除,则是null
oldValue: 键被更改之前的值

	// 监听storage事件
	EventUtil.addHandler(document, 'storage', function(event) {
		alert('Storage changed for ' + event.domain)
	})

6.限制

http://dev-test.nemikor.com/web-storage/support-test

IndexedDB

1.数据库

全称:Indexed Database API,是在浏览器中保存结构化数据的一种数据库。
IndexedDB设计的操作完全是异步进行的
差不多每一次操作IndexedDB操作都需要注册onerror 或 onsucdess事件处理程序

	var indexedDB = window.indexedDB || window.msIndexedDB || window.mozIndexedDB || window.webkitIndexedDB

最大特色:使用对象保存数据,而不是表

// 1.与数据库建立连接
	var request, database
	
	request = indexedDB.open('admin');
	request.onerror = function (event) {
		alert('Something bad happened while trying to open: ' + event.target.errorCode)
	}
	request.onsuccess = function (event) {
		database = event.target.result
	}
	
	// 指定版本号
	if (database.version != '1.0') {
		request = database.setVersion('1.0')
		request.onerror = function (event) {
			alert('Something bad happened while trying to set version: ' + event.target.errorCode)
		}
		request.onsuccess = function(event) {
			alert('Database initialization complate. Database name: ' + database.name + ',version: ' + database.version)
		}
	} else {
		alert('Database already initialized. Database name: ' + database.name + ',version: ' + datbase.version)
	}

2.对象存储空间

	// 2.使用对象存储空间
	
	// 创建一条记录
	var user = {
		username: '007',
		firstName: 'James',
		lastName: 'Bond',
		psaaword: 'foo'
	}
	// 保存上述记录并创建对象存储空间
	var store = db.createObjectStore('user', { keyPath: 'username' })
	
	// users保存着一批用户对象
	var i = 0,
		len = user.length
	while (i < len) {
		store.add(users[i++])
	}
	
	// users中保存着一批用户对象
	var i = 0,
		request,
		requests = [],
		len = users.length
	while (i < len) {
		request = store.add(users[i++])
		request.onerror = function() {
			// 处理错误
		}
		request.onsuccess = function () {
			// 处理成功
		}
		requests.push(request)
	}

3.事物

想要读取或者修改数据,都要通过事务来组织所有操作

	// 创建事务
	// db database 数据库对象
	// 加载所有数据(只读)
	var transaction = db.transaction()
	// 加载users存储空间中的数据(只读)
	var transaction = db.transaction('users')
	// 访问多个对象的存储空间(只读)
	var transaction = db.transaction(['users', 'anotherStore'])
	
	// IDBTransaction接口定义常量:只读 READ_ONLY(0) 读写 READ_WRITE(1) 改变 VERSION_CHANGE(2)
	
	// IDBTransaction IE10+ Firefox4+ webkitIDBTransaction Chrome
	var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction
	// 读写
	var transaction = db.transaction('users', IDBTransaction.READ_WRITE)
	
	// 使用objectStore()方法并传入存储空间名称,可以访问特定的存储空间。
	// get()取得值 delete()删除对象 clear()删除所有对象
	var request = db.transaction('users').objectStore('users').get('007')
	request.onerror = function (event) {
		alert('Did not get the object!')
	}
	request.onsuccess = function (event) {
		var result = event.target.result
		alert(result.firstName) // 'James'
	}
	
	// 提供事务级的状态信息
	transaction.onerror = function (event) {
		// 整个事物都被取消
	}
	transaction.oncomplete = function (event) {
		// 整个事务都成功完成
	}
	// incomplete事件的event访问不到get()请求返回的任何数据,必须onsuccess事件处理程序中才能访问到

4.使用游标查询

使用事务可以直接通过已知的键检索单个对象
在事务中创建游标,可以检索多个对象
游标就是指向结果集的指针

// openCursor()创建游标,返回的是一个请求对象,因此必须为该对象指定onsuccess和onerror事件处理程序
	var store = db.transaction('users').objectStore('users'),
		request = store.openCursor()
	request.onsuccess = function (event) {
		// 处理成功
		// event.target.result可取得存储空间中的下一个对象
		// 有值时是一个IDBCursor的实例,无值时为null
		/*  IDBCursor实例有以下几个属性:
		 ** direction: 数值,表示游标移动的方向 。
		 ** 		   默认值 IDBCursor.NEXT(0) 下一项
		 **			   IDBCursor.NEXT_NO_DUPLICATE(1) 下一个不重复的项
		 **			   DBCursor.PREV(2) 前一项
		 **			   IDBCursor.PREV_NO_DUPLICATE 前一个不重复的项
		 **	key: 对象的键
		 ** value: 实际的对象
		 ** primaryKey: 游标使用的键。可能是对象键或索引键
		 **/
		 // 检索某一个对象的信息
		 request.onsuccess = function (event) {
			 var cursor = event.target.result
			 if (cursor) { // 必须要检查
				// cursor.value是一个对象
				console.log('Key: ' + cursor.key + ',Value: ' + JSON.stringify(cursor.value))
			 }
		 }
	}
	request.onerror = function (event) {
		// 处理失败
	}
	
	// 调用update()也会创建一个新请求
	// 调用update()方法可以用指定的对象更新当前游标的value
	request.onsuccess = function (event) {
		var cursor = event.target.result,
			value,
			updateRequest;
		if (cursor) { // 必须要检查
			if (cursor.key == 'foo') {
				value = cursor.value		// 取得当前的值
				value.password = 'magic!'	// 更新密码
				
				updateRequest = cursor.update(value) // 请求保持更新
				updateRequest.onsuccess = function () {
					// 处理成功
				}
				updateRequest.onerror = function () {
					// 处理失败
				}
			}
		}
	}
	
	// 调用delete()也会创建一个新请求
	// 调用delete()方法可以删除相应记录
	request.onsuccess = function (event) {
		var cursor = event.target.result,
			value,
			deleteRequest;
		if (cursor) { // 必须要检查
			if (cursor.key -== 'foo') {
				deleteRequest = cursor.delete() // 请求删除当前项
				deleteRequest.onsuccess = function () {
					// 处理成功
				}
				deleteRequest.onerror = function () {
					// 处理失败
				}
			}
		}
	}
	
	// 如果当前事务没有修改对象存储空间,update()和delete()会抛出错误
	
	/* 默认情况下,每个游标只发起一次请求。要想发起另一次请求,必须调用下面的一个方法:
	** continue(key): 移动到“结果集”中的下一项。
	**				  参数key可选,不指定时游标移动到下一项,指定时游标移动到指定键的位置
	** advance(count): 向前移动count指定的项数
	**/
	
	// 遍历了对象存储空间中的所有项
	request.onsuccess = function (event) {
		var cursor = event.target.result
		if (cursor) { // 必须要检查
			console.log('Key: ' + cursor.key + ', Value: ' + JSON.stringfy(cursor.value))
			cursor.continue() // 移动到下一项
			// 使用continue()会触发另一次请求
		} else {
			console.log('Done!')
		}
	}

5.键范围

键范围为使用游标增添了一些灵活性
IDBKeyRange IE10+ Firefox 4+ webkitIDBKeyRange Chrome

	// 解决浏览器差异
	var IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange
	
	// 四种定义键范围的方式
	
	// 1.only() 传入想要取得的对象的键
	var onlyRange = IDBKeyRange.only('007')
	// 相当于直接访问存储空间并调用 get('007')
	
	// 2.指定结果集的下界(游标开始的位置)
	// 从键为 007 开始移动到最后
	var lowerRange = IDBKeyRange.lowerBound('007')
	
	// 从键为 007 的下一个对象开始移动到最后
	var lowerRange = IDBKeyRange.lowerBound('007', true)
	
	// 3.定义上界
	
	// 从头开始到键为 ace 的对象为止
	var upperRange = IDBKeyRange.upperBound('ace')
	
	// 从头开始到键为ace的对象的上一个对象为止
	var upperRange = IDBKeyRange.upperBound('ace', true)
	
	// 4.同时指定上下界
	
	// 从键 007 的对象开始到键为ace的对象为止
	var boundRange = IDBKeyRange.bound('007', 'ace')
	
	// 从键 007 的对象的下一个对象开始到键为ace的对象为止
	var boundRange = IDBKeyRange.bound('007', 'ace', true)
	
	// 从键 007 的对象的下一个对象开始到键为ace的对象的上一个对象为止
	var boundRange = IDBKeyRange.bound('007', 'ace', truetrue)
	
	// 从键 007 的对象开始到键为ace的对象的上一个对象为止
	var boundRange = IDBKeyRange.bound('007', 'ace', false, true)
	
	// 定义键范围后传给openCursor()方法得到一个符合相应约束条件的游标
	var store = db.transaction('users').objectStore('users'),
		range = IDBKeyRange.bound('007', 'acs'),
		request = store.openCursor(range)
		
	request.onsuccess = function(event) {
		var cursor = event.target.result
		if (cursor) { // 必须要检查
			console.log('Key: ' + cursor.key + ', Value: ' + JSON.stringfy(cursor.value))
			cursor.continue()// 移动到下一项
		} else {
			console.log('Done!')
		}
	}

6.设定游标方向

1.IDBCursor.NEXT IDBCursor.NEXT_NO_DUPLICATE
游标从存储空间第一项开始,调用continue()或advance()前进到最后一项

2.IDBCursor.PREV IDBCursor.PREV_NO_DUPLICATE
游标从存储空间最后一项开始,逐个迭代至第一个对象
使用上两项打开游标时,每次调用continue()或advance()都会在存储空间向后而不是向前移动

openCursor()可以接收两个参数,第一个IDBKeyRange的实例,第二个表示方向的数值常量

	// 消除浏览器差异
	var IDBCursor = window.IDBCursor || window.webkitIDBCursor

	// 方向默认参数是IDBCursor.NEXT 跳过重复项是IDBCursor.NEXT_NO_DUPLICATE
	// 如果让游标跳过重复项
	var store = db.transaction('users').objectStore('users'),
		// null 包含所有对象
		request = store.openCursor(null, IDBCursor.NEXT_NO_DUPLICATE)
		
	var store = db.transaction('users').objectStore('users'),
		// null 包含所有对象
		request = store.openCursor(null, IDBCursor.PREV)			

7.索引

例如要通过用户ID和用户名两种方式保存用户资料,需要通过这两个键来存取记录
可以将用户ID作为主键,为用户名创建索引

	// 创建索引
	var store = db.transaction('users').objectStore('users'),
		index = store.createIndex('username', 'username', { unique: false })
	// createIndex()第一个参数 索引的名字 第二个参数 索引的属性的名字 第三个参数 一个包含unique属性的选项对象
	// 返回 IDBIndex 的实例
	// 调用index()能返回同样的一个实例
	
	var store = db.transaction('users').objectStore('users'),
		index = store.index('username'),
		request = index.openCursor();
		
	request.onsuccess = function () {
		// 处理成功
	}
	
	// 创建一个特殊的只返回每条记录主键的游标 openKeyCursor()
	// 接收参数与openCursor()一样,但是event.result.key中保存着索引键,event.result.value 保存着主键
	
	var store = db.transaction('users').objectStore('users'),
		index = store.index('username'),
		request = index.openKeyCursor();
		
	request.onsuccess = function (event) {
		// 处理成功
		// event.result.key 保存索引键
		// event.result.value 保存主键
	}
	
	// get()方法传入相应的索引键可以取得一个对象
	var store = db.transaction('users').objectStore('users'),
		index = store.index('username'),
		request = index.get('007')
		
	request.onsuccess = function (event) {
		// 处理成功
	}
	request.onerror = function (event) {
		// 处理失败
	}
		
	// getKey()方法可以根据给定的索引键取得主键
	// event.result.value等于主键的值,而不是包含整个对象
	var store = db.transaction('users').objectStore('users'),
		index = store.index('username'),
		request = index.getKey('007');
	
	request.onsuccess = function (event) {
		// 处理成功
		// event.result.key 保存索引键 event.result.value 保存主键
	}
	
	/* 通过IDBIndex对象的下列属性都可以取得有关索引的相关信息
	** name: 索引的名字
	** keyPath: 传入的对象存储空间
	** objectStore: 索引的对象存储空间
	** unique: 表示索引键是否唯一的布尔值
	**/
	// 存储对象的 indexName 属性可以访问到为该空间建立的所有索引
	// 编辑每个索引并打印信息
	var store = db.transaction('users').objectStore('users'),
		indexNames = store.indexNames,
		index,
		i = 0,
		len = indexNames.length;
	
	while (i < len) {
		index = store.index(indexNames[i++])
		console.log('Index name: ' + index.name + ', KeyPath: ' + index.keyPath + ', Unique: ' + index.unique)
	}
	
	// 删除索引
	var store = db.transaction('users').objectStore('users');
	store.deleteIndex('username')
	// 删除索引不会影响对象存储空间中的数据,所以这个操作没有任何回调函数

8.并发问题

虽然IndexedDB提供的是异步API,但仍然存在并发问题

	// 浏览器的两个不同的标签页打开了同一个页面,那么一个页面试图更新另一个页面尚未准备就绪
	var request, database;
	
	request = indexedDB.open('admin');
	request.onsuccess = function (event) {
		database = event.target.result
		
		database.onversionchange = function() {
			database.close()
		}
	}
	
	// 想要更新数据库的版本但是另一个标签页已经打开数据库
	var request = database.setVersion('2.0')
	request.onblocked = function () {
		alert('Please close all other tabs and again.')
	}
	
	request.onsuccess = function () {
		// 处理成功,继续
	}

9.限制

1.IndexedDB和Web Storage类似只能由同源(相同协议,域名和端口)页面操作,不能跨域共享信息
即www.wrox.com和p2p.wrox.com的数据库完全独立

2.每个来源数据库占用磁盘空间有限
Firefox 4+ 50MB Chrome 5MB 移动端 Firefox超过5MB请求用户许可

3.Firefox不允许本地文件访问IndexedDB

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值