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数据库只能由同源页面操作
- 数据库占据的磁盘空间有限制