JavaScript

  • 二十三、离线应用与客户端存储
  • 23.1 离线检测
  • 23.2 应用缓存
  • 23.3 数据存储
  • 23.3.1 Cookie
  • 23.3.1.1 限制
  • 23.3.1.2 cookie的构成
  • 23.3.1.3 Javascript中的cookie
  • 23.3.1.4 子cookie
  • 23.3.1.5 关于cookie的思考
  • 23.3.2 IE用户数据
  • 23.3.3 Web存储机制
  • 23.3.3.1 Storage类型
  • 23.3.3.2 sessionStorage对象
  • 23.3.3.3 globalStorage对象
  • 23.3.3.4 localStorage对象
  • 23.3.3.5 storage事件
  • 23.3.3.6 限制
  • 23.3.4 IndexedDB
  • 23.3.4.1 数据库
  • 23.3.4.2 对象存储空间
  • 23.3.4.3 事务
  • 23.3.4.4 使用游标查询
  • 23.3.4.5 键范围
  • 23.3.4.6 设定游标方向
  • 23.3.4.7 索引
  • 23.3.4.8 并发问题
  • 23.3.4.9 限制


二十三、离线应用与客户端存储

23.1 离线检测

  • navigator.onLine 属性
  • 属性值为true表示设备能上网
  • window对象的online 事件
  • window对象的offline 事件

23.2 应用缓存

  • application cache(appcache)
  • 启用缓存
  • 在页面中的 <html> 元素上增加 manifest 特性,并与缓存清单(cache manifest) 文件关联
  • 文档加载
  • 浏览器会自动缓存添加有 manifest 特性的页面
  • 清单文件修改后浏览器会自动更新缓存
  • 先将新的缓存文件下载到临时空间
  • 待所有缓存文件下载完毕后再更新到正式缓存中
<html manifest="/offline.manifest" >
...
</html>


<!--offline.manifest-->
---------------
CACHE MANIFEST
#Comment

file.js
file.css
---------------
  • applicationCache对象
  • status属性
  • 0 无缓存
  • 1 闲置
  • 2 检查中,即正在下载描述文件并检查更新
  • 3 下载中,即应用缓存正在下载描述文件中指定的资源
  • 4 更新完成,所有新的缓存资源都已下载完毕,待执行更新
  • 5 废弃,即描述文件不存在
  • 事件
  • checking 查找更新时触发
  • error
  • noupdate
  • downloading 开始下载缓存文件
  • progress 文件下载中持续触发
  • updateready 文件下载完毕
  • cached 缓存可用
  • 方法
  • update()
  • 可手动触发浏览器检查更新
  • swapCache()
  • 启用下载好的新缓存

23.3 数据存储

23.3.1 Cookie

  • 服务器将会话信息保存到客户端,之后每次客户端发送请求时都会携带该信息
  • 服务器发送 Set-Cookie HTTP头作为响应
  • 客户端为每次请求添加Cookie HTTP头
HTTP/1.1 200 OK
Content-type: text/html
Set-Cookie: name=value
Other-header: other-header-value
GET /index.html HTTP/1.1
Cookie: name=value
Other-header: other-header-value

23.3.1.1 限制

  • 只在请求创建该Cookie的域名时才会携带该Cookie
  • 浏览器对Cookie的数量以及单个Cookie的大小有限制

23.3.1.2 cookie的构成

  • 名称
  • 不区分大小写
  • 在向该域发送请求时会携带cookie
  • 路径
  • 将携带cookie的请求限制在某个域下的某个路径,只有向该路径发送请求才会携带cookie
  • 失效时间
  • 表示cookie何时应该被删除的时间戳,默认会话结束后删除
  • 安全标志
  • 设定后只有使用SSL连接才会将cookie发送到服务器
  • 设定方式为在响应头中加入"secure"
HTTP/1.1 200 OK
Content-type: text/html
Set-Cookie: name=value; domain=.wrox.com; path=/; secure
Other-header: other-header-value

23.3.1.3 Javascript中的cookie

  • document.cookie
  • 读取
  • 返回所有cookie组成的字符串,例如,“name1=value1;name2=value2”
  • 字符串经过了URL编码
  • 写入
  • 给document.cookie赋值时,只要键是新的键,就是添加到cookie集合中,重复键则为覆盖。例如,document.cookie = “name=Nicholas”
  • 删除
  • 将失效时间设置为过期时间即可
  • 由于读取时获取的是全部cookies,而写入时需要进行URL编码,所以可以自定义工具对象,提供读取、写入、操作的简便方法

23.3.1.4 子cookie

  • 为了解决浏览器单个域名下cookie个数的限制,将多个cookie放入同一个key下的value中
  • 例如,name=“name1=value1&name2=value2”
  • 读取
  • 先从document.cookie中截取主cookie
  • var cookieStart = document.cookie.indexOf(cookieName)
  • var cookieEnd = document.cookie.indexOf( " ; " , cookieStart )
  • 从主cookie的值中截取所有子cookie作为结果对象的属性
  • var subCookies = cookieValue.split("&")
  • var parts = subCookies[i].split("=")
  • var result = {}; result[ parts[0] ] = parts[1]
  • 通过子cookie名称与结果对象的属性名进行匹配,获取子cookie
  • var subCookie = result[ subName ]
  • 写入
  • 先获取主cookie下的所有子cookie的结果对象
  • 将写入的值加入到结果对象中
  • 将所有子cookie形成的结果对象转化为"name=value"形式的数组
  • 循环实例属性 hasOwnProperty()
  • 将数组拼接成以"&"为连接符的字符串
  • 替换主cookie
  • 删除
  • 先获取主cookie下的所有子cookie的结果对象
  • 删除结果对象中的某个属性
  • 将所有子cookie形成的结果对象转化为"name=value"形式的数组
  • 循环实例属性 hasOwnProperty()
  • 将数组拼接成以"&"为连接符的字符串
  • 替换主cookie

23.3.1.5 关于cookie的思考

  • cookie信息越大,完成对服务器请求的时间也就越长
  • 每次请求都会携带给服务器

23.3.2 IE用户数据

  • 微软通过自定义行为引入持久化用户数据
  • 必须在元素上使用CSS指定userData行为
  • <div id=“dataStore” style=“behavior:url(#default#userData)”></div>
  • 写入数据
  • 通过DOM给元素设置属性
  • dataStore.setAttribute(“name”,“Nicholas”)
  • 调用save()方法将数据提交到浏览器缓存中,并设置数据空间的名称
  • dataStore.save(“BookInfo”);
  • 读取数据
  • 将数据空间的数据载入到元素
  • dataStore.load(“BookInfo”);
  • 通过DOM读取元素属性
  • dataStore.getAttribute(“name”)
  • 删除数据
  • 删除属性
  • removeAttribute(“name”)
  • 调用save()提交更改

23.3.3 Web存储机制

  • Web Storage
  • 特点
  • 数据需要控制在客户端上,无须持续将数据发送给服务器
  • 存储容量大
  • 类型
  • sessionStorage
  • globalStorage
  • 作为window对象的属性

23.3.3.1 Storage类型

  • 只能存储字符串
  • 方法
  • clear()
  • getItem(name)
  • key(index)
  • for遍历时使用
  • removeItem(name)
  • setItem(name, value)

23.3.3.2 sessionStorage对象

  • sessionStorage对象
  • 特点
  • 存储特定于某个会话的数据,在浏览器关闭后数据消失
  • 刷新后依旧可用
  • 只能由最初给对象存储数据的页面访问
  • 保存的数据在本地运行时不可用
  • 写入方式
  • 部分浏览器支持同步,部分浏览器支持异步
  • 可以调用commit()方式强制写入磁盘
  • 在commit()方法前可以调用begin()方法,通过事务的方式进行数据保存

23.3.3.3 globalStorage对象

  • globalStorage
  • 特点
  • 跨越会话存储数据
  • 需要先指定可以访问的域,通过域名、协议和端口匹配
  • 可以一直保留在磁盘上
  • 读写方式
  • globalStorage[“wrox.com”].name = “Nicholas” ;
  • var name = globalStorage[“wrox.com”].name;
  • globalStorage[“wrox.com”]才是Storage的实例
  • wrox.com及其子域可以访问该存储空间

23.3.3.4 localStorage对象

  • localStorage与globalStorage类似,但不能自定义指定访问域
  • 规则事先设定好了,页面必须来自同一域名、协议、端口
  • 相当于globalStorage对象将域名限定为location.host

23.3.3.5 storage事件

  • 对Storage对象进行任何修改,都会触发storage事件
  • event对象实例属性
  • domain
  • key
  • newValue
  • oldValue

23.3.3.6 限制

  • 存储空间大小以每个来源(协议、域、端口)为单位进行限制

23.3.4 IndexedDB

  • Indexed Database API
  • 浏览器中保存结构化数据的数据库
  • 替代Web SQL Database API
  • 保存和读取JavaScript对象,支持查询及搜索
  • 异步进行,每次操作需要注册事件处理程序以处理结果
  • IndexedDB为API宿主的全局对象

23.3.4.1 数据库

  • IndexedDB
  • 特点
  • 使用对象保存数据
  • 一组位于相同命名空间下的对象的集合
  • 基本使用
  • indexedDB.open(databaseName)
  • 已存在名称为databaseName的数据库,则打开
  • 否则,则创建
  • 二者都会返回 IDBRequest 对象
  • 可以在IDBRequest对象上注册事件处理程序对请求结果进行处理
  • 请求成功 onsuccess
  • event.target.result中会保存IDBdatabase实例
  • 可以设置IDBdatabase实例的版本号
  • 调用database.setVersion(versionStr)
  • 同样返回IDBRequest对象
  • 请求失败 onerror
  • event.target.errorCode 中会保存错误码
var indexedDB = window.indexedDB || window.msIndexedDB || window.mozIndexedDB || window.webkitIndexedDB ;
var request , database ;
request = indexedDB.open("admin");
request.onsuccess = function(event){
	database = event.target.result ;
};
request.onerror = function(event){
	alert("Something bad happened while trying to open" + 
	event.target.errorCode);
};

23.3.4.2 对象存储空间

  • 对象存储空间
  • 存储的对象的集合,或者理解为数据库表
  • 要保存的对象,可以理解为表中的记录
  • 创建存储空间
  • database.createObjectStore( storeName, { keyPath: propertyName } )
  • keyPath为主键
  • 添加、修改数据
  • store.add()
  • 添加已存在的键值会报错
  • store.put()
  • 添加已存在的键值会覆盖原对象

23.3.4.3 事务

  • 除了创建存储空间,接下来的任何操作都需要通过事务来完成
  • 创建事务
  • database.transaction()
  • 传入参数
  • 参数一:要访问的存储空间
  • 可以是多个,传入字符串数组
  • 参数二:访问数据的方式
  • READ_ONLY(0)
  • READ_WRITE(1)
  • VERSION_CHANGE(2)
  • 访问存储空间
  • transaction.objectStore( storeName )
  • 可以在返回值上进行add()、put()、delete( keyPath )、get( keyPath )、clear()等操作
  • 支持complete事件,但在event对象中无法访问到结果数据

23.3.4.4 使用游标查询

  • 游标
  • 查找多个对象时使用
  • 指向结果集的第一项的指针
  • 创建游标
  • store.openCursor()
  • success事件中event.target.result保存了IDBCursor实例,可以访问下一个对象
  • IDBCursor
  • direction 游标移动的方向
  • IDBCursor.NEXT(0)
  • IDBCursor.NEXT_NO_DUPLICATE(1) 下一个不重复的项
  • IDBCursor.PREV(2)
  • IDBCursor.PREV_NO_DUPLICATE(3)
  • 在创建游标时可以作为第二个参数对游标进行设定
  • key
  • value
  • primaryKey 游标使用的键(对象键或者索引键)
  • 通过游标对存储空间进行删改查的操作
var request = store.openCursor();
request.onsuccess = function(event){
	var cursor = event.target.result,
		value,
		updateRequest;
	if(cursor){
		//查询
		console.log("Key:" + cursor.key + ",Value:"+
			JSON.stringify(cursor.value) );
			
		if(cursor.key =="foo"){
			//修改
			value = cursor.value;
			value.password = "magic!";
			updateRequest = cursor.update(value);
			updateRequest.onsuccess = function(event){
				...
			};
			
			//删除
			deleteRequest = cursor.delete();
			deleteRequest.onsuccess = function(event){
				...
			};
	}
};
  • 游标的移动
  • continue( key ) 不指定键值时为移动到下一项
  • advance( count )
  • 游标使用移动之前的相同的请求,事件处理程序也会重用
var request = store.openCursor();
request.onsuccess = function(event){
	var cursor = event.target.result;
	if(cursor){
		console.log("Key:" + cursor.key + ",Value:"+
			JSON.stringify(cursor.value) );
		cursor.continue();
	}
	else{
		console.log("Done!");
	}
};

23.3.4.5 键范围

  • 通过键范围可以获得指向部分符合条件的结果集
  • 键范围
  • IDBKeyRange类型
  • 定义方式
  • IDBKeyRange.only( key )
  • IDBKeyRange.lowerBound( key , bool )
  • true 表示跳过该键,取其下一项
  • IDBKeyRange.upperBound( key , bool )
  • IDBKeyRange.bound( lowerKey , upperKey , lowerbool , upperbool)
var store = db.transaction("users").objectStore("users"),
	range = IDBKeyRange.bound("007" , "ace"),
	request = store.openCursor(range);
request.onsuccess = function(event){
	...
};

23.3.4.6 设定游标方向

  • store.openCursor( null, IDBCursor.NEXT_NO_DUPLICATE)
  • cursor.continue() 和 cursor.advance()只触发移动,方向由创建游标时进行设定

23.3.4.7 索引

  • 通过索引可以为存储空间指定多个键
  • 索引
  • 创建索引
  • store.createIndex( IndexName, PropertyName, options )
  • options
  • { unique:false }
  • IDBIndex 类型
  • name 即 IndexName
  • keyPath 即 PropertyName
  • objectStore
  • unique bool类型
  • 获取已经存在的索引
  • store.index( IndexName )
  • 获取存储空间的所有索引的名称集合
  • store.indexNames属性
  • 通过索引获取主键
  • index.getKey( IndexName )
  • 删除索引
  • store.deleteIndex( IndexName )
  • 创建游标
  • index.openCursor()
  • event.result.key 为索引键
  • index.openKeyCursor()
  • event.result.key 为索引键
  • event.result.value 为主键

23.3.4.8 并发问题

  • 当两个不同的标签页打开同一个页面,一个标签页试图更新数据库(版本),而另一个页面打开了数据库,则会发生并发问题
  • 解决方式
  • 利用versionchange事件在版本更新前将已打开的数据库关闭,只保留发出更新版本指令的页面的数据库连接
  • 对于发出更新版本指令的页面,利用blocked事件提醒用户关闭其他标签页

23.3.4.9 限制

  • IndexedDB数据库只能由同源页面操作
  • 数据库占据的磁盘空间有限制