二十三、离线应用与客户端存储
23.1 离线检测
navigator.onLine 属性
window对象的online 事件 window对象的offline 事件
23.2 应用缓存
application cache(appcache)
启用缓存
在页面中的 <html> 元素上增加 manifest 特性,并与缓存清单(cache manifest) 文件关联 文档加载
浏览器会自动缓存添加有 manifest 特性的页面 清单文件修改后浏览器会自动更新缓存
先将新的缓存文件下载到临时空间 待所有缓存文件下载完毕后再更新到正式缓存中
< html manifest = " /offline.manifest" >
...
</ html>
---------------
CACHE MANIFEST
#Comment
file.js
file.css
---------------
applicationCache对象
status属性
0 无缓存 1 闲置 2 检查中,即正在下载描述文件并检查更新 3 下载中,即应用缓存正在下载描述文件中指定的资源 4 更新完成,所有新的缓存资源都已下载完毕,待执行更新 5 废弃,即描述文件不存在 事件
checking 查找更新时触发 error noupdate downloading 开始下载缓存文件 progress 文件下载中持续触发 updateready 文件下载完毕 cached 缓存可用 方法
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何时应该被删除的时间戳,默认会话结束后删除 安全标志
设定后只有使用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"形式的数组
将数组拼接成以"&"为连接符的字符串 替换主cookie 删除
先获取主cookie下的所有子cookie的结果对象 删除结果对象中的某个属性 将所有子cookie形成的结果对象转化为"name=value"形式的数组
将数组拼接成以"&"为连接符的字符串 替换主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”) 删除数据
23.3.3 Web存储机制
Web Storage
特点
数据需要控制在客户端上,无须持续将数据发送给服务器 存储容量大 类型
sessionStorage globalStorage 作为window对象的属性
23.3.3.1 Storage类型
只能存储字符串 方法
clear() getItem(name) key(index)
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 } )
添加、修改数据
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 )
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 )
IDBIndex 类型
name 即 IndexName keyPath 即 PropertyName objectStore unique bool类型 获取已经存在的索引
获取存储空间的所有索引的名称集合
通过索引获取主键
index.getKey( IndexName ) 删除索引
store.deleteIndex( IndexName ) 创建游标
index.openCursor()
index.openKeyCursor()
event.result.key 为索引键 event.result.value 为主键
23.3.4.8 并发问题
当两个不同的标签页打开同一个页面,一个标签页试图更新数据库(版本),而另一个页面打开了数据库,则会发生并发问题
解决方式
利用versionchange事件在版本更新前将已打开的数据库关闭,只保留发出更新版本指令的页面的数据库连接 对于发出更新版本指令的页面,利用blocked事件提醒用户关闭其他标签页
23.3.4.9 限制
IndexedDB数据库只能由同源页面操作 数据库占据的磁盘空间有限制