一.离线检测
开发离线应用的第一步是要知道设备是在线还是离线,HTML5为此定义了一个navigator.onLine属性,这个属性为true表示设备可以上网
1 if (navigator.onLine ) {
2 //在线
3 } else {
4 // 离线
5 }
除此之外HTML5还定义了2个时间online 和 offline
EventUtil.addHandler(window, "online", function(){
// 在线
})
EventUtil.addHandler(window, "offline", function(){
// 离线
})
二.应用缓存
概念:HTML5的应用缓存(application cache),简称为appcache,是专美为开发离线Web应用而设计的。Appcache就是从浏览器缓存中分出来的一块缓存区。
原理:是基于一个manifest文件(缓存清单文件,后缀为. appcache)的缓存机制(不是存储技术),通过这个文件上的清单解析离线存储资源,这些资源就会像cookie一样被存储了下来。之后当网络在处于离线状态时,浏览器会通过被离线存储的数据进行页面展示。
使用:将描述文件与页面关联起来,可以在<html>中的manifest属性中指定这个文件的路径,例如
<html manifest="/offline.manifest">
虽然应用缓存的意图是确保离线时资源可用,但也有相应的JavaScript API让你知道它在做什么,核心API时applicarionCache对象,这个队形有一个status属性,属性值是常量
- 0:无缓存
- 1:闲置
- 2:检查中
- 3:下载中
- 4:更新完成
- 5:废弃
而且应用缓存还有很多相关的事件,表示其状态的改变
- checking:在浏览器为应用缓存查找更新时触发
- error:在检查更新或下载资源期间发生错误时触发
- noupdate:在检查描述文件发现文件无变化时触发
- downloading:在开始下载应用缓存资源时触发
- progress:在文件下载应用缓存的过程中持续不断的触发
- updateready:在页面新的应用缓存下载完毕且可以通过swapCache()使用时触发
- cached:在应用缓存完整可用时触发
还可以诶调用update()方法手工干预,让应用缓存
applicationCache.uypdata()
//
一旦调用,应用缓存就会区检查描述文件设否更新,触发checking事件,然后继续执行后续操作。如果触发了cached事件,说明应用缓存已经准备就绪,不会再有其他操作发生了。如果触发了updateready事件,说明新版本的应用缓存已经咳痛,此时需要调用swapCache来启用新应用缓存
EventUtil.addHandler(applicationCache, "updateready", function(){
applicationCache.swapCache()
})
三.数据存储
1.Cookie
a.限制:cookie在性质上是绑定在特定的域名下的。当设定了一个cookie后,再给创建它的域名发送请求时,都会包含这个cookie
b.构成
名称:一个唯一确定cookie的名称,不区分大小写,实际应用还是区分好一定,因为有的服务器处理上会做大小写判断。
值:储存在cookie的字符串值。值必然被URL编码。
域:cookie对于那个域有效。
路径:对于制定域中的那个路径。
失效时间:表示cookie何时应该被删除的时间戳。
安全标志:指定后,cookie只有在使用SSL链接的时候才会发送到服务器
c.JavaScript中的cookie
在js中处理cookie有些复杂,即BOM的document.cooke属性并且所有名字和值都是经过URL编码的,所以要用decodeURLComponent()来解码。可以自己封装js方法来创建,更新,删除cookie。
目前我们用的比较多的就是cookie-js的库来处理,具体细节大家可以去看下源码。
d.子cookie
为了绕开浏览器的单域名下的cookie数限制,一些开发人员使用一种称为子cookie(suvcookie)的概念。
e.关于cookie其他思考
还有一类cookie被称为“HTTP专用cookie”。这个可以从浏览器或者服务器设置,但是只能从服务器端读取,因为JavaScript无法获取HTTP专用cookie的值。
2.Web存储机制
a.Storage类型
b.sessionStorage对象
c.globalStorage对象
涉及访问限制
d.localStorage对象
e.storage事件
domain:发生变化的存储空间的域名
key:设置或者删除的键名。
newValue:如果是设置值,则是新值,如果是删除键,则是null
oldValue:键被更新前的值
3.IndexedDB(浏览器数据库)
IndexedDB 允许储存大量数据,提供查找接口,还能建立索引。
特点:
- 键值对储存
- 异步
- 支持事务
- 同源限制
- 存储空间大,一般不少于250MB,甚至没有上线
- 支持二进制储存
基本概念:
- 数据库:IDBDatabase 对象(数据库是一系列相关数据的容器。每个域名(严格的说,是协议 + 域名 + 端口)都可以新建任意多个数据库。IndexedDB 数据库有版本的概念。同一个时刻,只能有一个版本的数据库存在。如果要修改数据库结构(新增或删除表、索引或者主键),只能通过升级数据库版本完成。)
- 对象仓库:IDBObjectStore 对象(每个数据库包含若干个对象仓库(object store)。它类似于关系型数据库的表格。)
- 索引:IDBIndex 对象(为了加速数据的检索,可以在对象仓库里面,为不同的属性建立索引。)
- 事务:IDBTransaction 对象(数据记录的读写和删改,都要通过事务完成。事务对象提供
error
、abort
和complete
三个事件,用来监听操作结果。) - 操作请求:IDBRequest 对象
- 指针:IDBCursor 对象
- 主键集合:IDBKeyRange 对象
操作流程:
打开数据库:
使用indexedDB.open()
方法
var request = window.indexedDB.open(databaseName, version); // 这个方法接受两个参数,第一个参数是字符串,表示数据库的名字。如果指定的数据库不存在,就会新建数据库。第二个参数是整数,表示数据库的版本。如果省略,打开已有数据库时,默认为当前版本;新建数据库时,默认为1
indexedDB.open()
方法返回一个 IDBRequest 对象。这个对象通过三种事件error
、success
、upgradeneeded
,处理打开数据库的操作结果。
涉及的事件
a.error事件表示打开数据库失败。
request.onerror = function (event) {
console.log('数据库打开报错');
};
b.success事件表示成功打开数据库
var db;
request.onsuccess = function (event) {
db = request.result;
console.log('数据库打开成功');
};
这时通过request对象的result属性拿到数据库对象。
c.upgradeneeded事件表示数据库升级
var db;
request.onupgradeneeded = function (event) {
db = event.target.result;
}
这时通过事件对象target.result属性,拿到数据库实例。
新建数据库
新建数据库与打开数据库是同一个操作。如果指定的数据库不存在,就会新建。不同之处在于,后续的操作主要在upgradeneeded事件的监听函数里面完成,因为这时版本从无到有,所以会触发这个事件。
通常,新建数据库以后,第一件事是新建对象仓库(即新建表)。
request.onupgradeneeded = function(event) {
db = event.target.result;
var objectStore = db.createObjectStore('person', { keyPath: 'id' });
}
上面代码中,数据库新建成功以后,新增一张叫做person的表格,主键是id。
更好的写法是先判断一下,这张表格是否存在,如果不存在再新建。
request.onupgradeneeded = function (event) {
db = event.target.result;
var objectStore;
if (!db.objectStoreNames.contains('person')) {
objectStore = db.createObjectStore('person', { keyPath: 'id' });
}
}
主键(key)是默认建立索引的属性。比如,数据记录是{ id: 1, name: '张三' },那么属性可以作为主键。主键也可以指定为下一层对象的属性,比如{ foo: { bar: 'baz' } }的foo.bar也可以指定为主键。
如果数据记录里面没有合适作为主键的属性,那么可以让 IndexedDB 自动生成主键。
var objectStore = db.createObjectStore(
'person',
{ autoIncrement: true }
);
上面代码中,指定主键为一个递增的整数。
新建对象仓库以后,下一步可以新建索引。
request.onupgradeneeded = function(event) {
db = event.target.result;
var objectStore = db.createObjectStore('person', { keyPath: 'id' });
objectStore.createIndex('name', 'name', { unique: false });
objectStore.createIndex('email', 'email', { unique: true });
}
上面代码中,IDBObject.createIndex()的三个参数分别为索引名称、索引所在的属性、配置对象(说明该属性是否包含重复的值)。
新增数据
新增数据指的是向对象仓库写入数据记录。这需要通过事务完成。
function add() {
var request = db.transaction(['person'], 'readwrite')
.objectStore('person')
.add({ id: 1, name: '张三', age: 24, email: 'zhangsan@example.com' });
request.onsuccess = function (event) {
console.log('数据写入成功');
};
request.onerror = function (event) {
console.log('数据写入失败');
}
}
add();
上面代码中,写入数据需要新建一个事务。新建时必须指定表格名称和操作模式("只读"或"读写")。新建事务以后,通过IDBTransaction.objectStore(name)方法,拿到 IDBObjectStore 对象,再通过表格对象的add()方法,向表格写入一条记录。写入操作是一个异步操作,通过监听连接对象的success事件和error事件,了解是否写入成功。
读取数据
读取数据也是通过事务完成。
function read() {
var transaction = db.transaction(['person']);
var objectStore = transaction.objectStore('person');
var request = objectStore.get(1);
request.onerror = function(event) {
console.log('事务失败');
};
request.onsuccess = function( event) {
if (request.result) {
console.log('Name: ' + request.result.name);
console.log('Age: ' + request.result.age);
console.log('Email: ' + request.result.email);
} else {
console.log('未获得数据记录');
}
};
}
read();
上面代码中,objectStore.get()方法用于读取数据,参数是主键的值。
遍历数据
遍历数据表格的所有记录,要使用指针对象 IDBCursor。
function readAll() {
var objectStore = db.transaction('person').objectStore('person');
objectStore.openCursor().onsuccess = function (event) {
var cursor = event.target.result;
if (cursor) {
console.log('Id: ' + cursor.key);
console.log('Name: ' + cursor.value.name);
console.log('Age: ' + cursor.value.age);
console.log('Email: ' + cursor.value.email);
cursor.continue();
} else {
console.log('没有更多数据了!');
}
};
}
readAll();
上面代码中,新建指针对象的openCursor()方法是一个异步操作,所以要监听success事件。
更新数据
更新数据要使用IDBObject.put()
function update() {
var request = db.transaction(['person'], 'readwrite')
.objectStore('person')
.put({ id: 1, name: '李四', age: 35, email: 'lisi@example.com' });
request.onsuccess = function (event) {
console.log('数据更新成功');
};
request.onerror = function (event) {
console.log('数据更新失败');
}
}
update();
上面代码中,put()方法自动更新了主键为1的记录。
删除数据
IDBObjectStore.delete()方法用于删除记录。
function remove() {
var request = db.transaction(['person'], 'readwrite')
.objectStore('person')
.delete(1);
request.onsuccess = function (event) {
console.log('数据删除成功');
};
}
remove();
使用索引
索引的意义在于,可以让你搜索任意字段,也就是说从任意字段拿到数据记录。如果不建立索引,默认只能搜索主键(即从主键取值)。
假定新建表格的时候,对name字段建立了索引。
objectStore.createIndex('name', 'name', { unique: false });
现在,就可以从name找到对应的数据记录了。
var transaction = db.transaction(['person'], 'readonly');
var store = transaction.objectStore('person');
var index = store.index('name');
var request = index.get('李四');
request.onsuccess = function (e) {
var result = e.target.result;
if (result) {
// ...
} else {
// ...
}
}
并发问题
浏览器两个不同的标签页打开同一个页面,name一个页面试图更新另一个页面尚未准备就绪的数据库的问题就有可能发生。
限制
只能同源,不能跨域,存在兼容问题,火狐不支持
success 事件