高程3总结#第23章离线应用与客户端存储
离线应用与客户端存储
离线检测
- HTML5定义了navigator.onLine属性来检测设备是在线还是离线。这个属性为true表示设备能上网,值为false表示设备离线。这个属性的关键是浏览器必须知道设备能否访问网络,从而返回正确的值
不同浏览器之间有小差异
- IE6+和Safari5+能够正确检测到网络已经断开,并将navigator.onLine的值转换为false
- Firefox3+和Opera10.6+支持navigator.onLine属性,但必须手工选中菜单项脱机工作才能让浏览器正常工作
- Chrome11以及之前版本始终将navigator.onLine属性设置为true
为了更好确定网络是否可用,HTML5还定义了两个事件onLine和offLine。当网络从离线变为在线或者从在线变为离线时,分别触发这两个事件
EventUtil.addHandler(window, "online", function(){ alert("Online"); }); EventUtil.addHandler(window, "offline", function(){ alert("Offline"); });
应用缓存
applicationCache 对象,status属性,属性的值是常量,表示应用缓存的如下当前状态。
- 0,无缓存,即没有与页面相关的应用缓存。
- 1,闲置,即应用缓存未得到更新。
- 2,检查中,即正在下载描述文件并检查更新。
- 3,下载中,即应用缓存正在下载描述文件中指定的资源。
- 4,更新完成,即应用缓存已经更新了资源,而且所有资源都已下载完毕,可以通过 swapCache()来使用了。
- 5,废弃,即应用缓存的描述文件已经不存在了,因此页面无法再访问应用缓存。
应用缓存还有很多相关的事件,表示其状态的改变。以下是这些事件。
- checking,在浏览器为应用缓存查找更新时触发。
- error,在检查更新或下载资源期间发生错误时触发。
- noupdate,在检查描述文件发现文件无变化时触发。
- downloading,在开始下载应用缓存资源时触发。
- progress,在文件下载应用缓存的过程中持续不断地触发。
- updateready,在页面新的应用缓存下载完毕且可以通过 swapCache() 使用时触发。
- cached,在应用缓存完整可用时触发。
数据存储
Cookie
- cookie在性质上是绑定在特定的域名下的。当设定了一个cookie后,再给创建它的域名发送请求时都会包含这个cookie。这个限制确保了储存在cookie中的信息只能让批准的接受者访问,而无法被其他域访问
cookie由浏览器保存的几部分组成
- 名称,一个唯一确定 cookie 的名称。cookie 名称是不区分大小写的,所以 myCookie 和 MyCookie被认为是同一个 cookie。然而,实践中最好将 cookie 名称看作是区分大小写的,因为某些服务器会这样处理 cookie。cookie 的名称必须是经过 URL 编码的。
- 值,储存在 cookie 中的字符串值。值必须被 URL 编码。
- 域,cookie 对于哪个域是有效的。所有向该域发送的请求中都会包含这个 cookie 信息。这个值可以包含子域(subdomain,如 www.wrox.com ),也可以不包含它(如. wrox.com ,则对于wrox.com的所有子域都有效)。如果没有明确设定,那么这个域会被认作来自设置 cookie 的那个域。
- 路径,对于指定域中的那个路径,应该向服务器发送 cookie。例如,你可以指定 cookie 只有从http://www.wrox.com/books/ 中才能访问,那么 http://www.wrox.com 的页面就不会发
送 cookie 信息,即使请求都是来自同一个域的。 - 失效时间,表示 cookie 何时应该被删除的时间戳(也就是,何时应该停止向服务器发送这个cookie)。默认情况下,浏览器会话结束时即将所有 cookie 删除;不过也可以自己设置删除时间。这个值是个 GMT 格式的日期(Wdy, DD-Mon-YYYY HH:MM:SS GMT),用于指定应该删除cookie 的准确时间。因此,cookie 可在浏览器关闭后依然保存在用户的机器上。如果你设置的失效日期是个以前的时间,则 cookie 会被立刻删除。
- 安全标志,指定后,cookie 只有在使用 SSL 连接的时候才发送到服务器。例如,cookie 信息只能发送给https://www.wrox.com ,而 http://www.wrox.com 的请求则不能发送 cookie。
基本的cookie操作有3种:读取、写入和删除
//设置 cookie CookieUtil.set("name", "Nicholas"); CookieUtil.set("book", "Professional JavaScript"); //读取 cookie 的值 alert(CookieUtil.get("name")); //"Nicholas" alert(CookieUtil.get("book")); //"Professional JavaScript" //删除 cookie CookieUtil.unset("name"); CookieUtil.unset("book") //设置 cookie,包括它的路径、域、失效日期 CookieUtil.set("name", "Nicholas", "/books/projs/", "www.wrox.com", new Date("January 1, 2010")); //删除刚刚设置的 cookie CookieUtil.unset("name", "/books/projs/", "www.wrox.com"); //设置安全的 cookie CookieUtil.set("name", "Nicholas", null, null, null, true);
确保删除cookie
//设置 cookie CookieUtil.set("name", "Nicholas"); CookieUtil.set("book", "Professional JavaScript"); //读取 cookie 的值 alert(CookieUtil.get("name")); //"Nicholas" alert(CookieUtil.get("book")); //"Professional JavaScript" //删除 cookie CookieUtil.unset("name"); CookieUtil.unset("book") //设置 cookie,包括它的路径、域、失效日期 CookieUtil.set("name", "Nicholas", "/books/projs/", "www.wrox.com", new Date("January 1, 2010")); //删除刚刚设置的 cookie CookieUtil.unset("name", "/books/projs/", "www.wrox.com"); //设置安全的 cookie CookieUtil.set("name", "Nicholas", null, null, null, true);
- 子cookie一般以查询字符串的格式进行格式化。然后这些值可以使用单个cookie进行存储和访问,而非对每个名称-值对儿使用不同的cookie存储
要获得一个子cookie,首先要遵循与获得cookie一样的基本步骤,但是在解码cookie值之前,需要按下面方法找出子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 = document.cookie.indexOf(";", cookieStart); if (cookieEnd == -1){ cookieEnd = document.cookie.length; } cookieValue = document.cookie.substring(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; }, //省略了更多代码 };
要设置子cookie,也有两种方法set()和setAll()
var SubCookieUtil = { 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 cookieText = 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 Date) { 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; }, //省略了更多代码 };
为了删除一个子cookie,首先必须获得包含在某个cookie中的所有子cookie,然后仅删除需要删除的那个子cookie,然后再将余下的子cookie的值保存为cookie的值
var SubCookieUtil = { //这里省略了更多代码 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); } }, unsetAll: function(name, path, domain, secure){ this.setAll(name, null, new Date(0), path, domain, secure); } };
IE用户数据
一旦元素使用了userData行为,那么就可以使用setAttribute()方法在上面保存数据了,为了将数据提交到浏览器缓存中,还必须调用save()方法,并告诉它要保存的数据空间的名字
var dataStore = document.getElementById("dataStore"); dataStore.setAttribute("name", "Nicholas"); dataStore.setAttribute("book", "Professional JavaScript"); dataStore.save("BookInfo")
Web存储机制
Storage类型
Storage的实例与其他对象类似
- clear(),删除所有值,Firefox中没有实现
- getItem(name),根据指定的名字name获取对应的值
- key(index),获得index位置处的值的名字
- removeItem(name),删除由name指定的名值对儿
- setItem(name,value),为指定的name设置一个对应的值
sessionStorage对象
可以使用setItem()或者直接设置新的属性类存储数据
//使用方法存储数据 sessionStorage.setItem("name", "Nicholas"); //使用属性存储数据 sessionStorage.book = "Professional JavaScript";
sessionStroage中有数据时,可以使用getItem()或者通过直接访问属性名来获取数据
//使用方法读取数据 var name = sessionStorage.getItem("name"); //使用属性读取数据 var book = sessionStorage.book;
globalStorage对象
可以通过方括号标记使用属性来实现
//保存数据 globalStorage["wrox.com"].name = "Nicholas"; //获取数据 var name = globalStorage["wrox.com"].name;
localStorage对象
可以像使用sessionStorage一样来使用
//使用方法存储数据 localStorage.setItem("name", "Nicholas"); //使用属性存储数据 localStorage.book = "Professional JavaScript"; //使用方法读取数据 var name = localStorage.getItem("name"); //使用属性读取数据 var book = localStorage.book;
storage事件
对Storage对象进行任何修改,都会在文档上触发storage事件,当通过属性或setItem()方法保存数据,使用delete操作符或removeItem()删除数据,或者调用clear()方法时,都会发生这个事件,这个事件的event对象有以下属性
- domain,发生变化的存储空间的域名
- key,设置或者删除的键名
- newValue,如果是设置值,则是新值,如果嘶吼删除键,则是null
- oldValue,键被更改之前的值
IndexedDB
数据库
- IndexedDB最大的特色就是使用对象保存数据,而不是使用表来保存数据
使用IndexedDB第一步是打开它,将要打开的数据库名传给indexedDB.open()。如果传入的数据库已经存在,就会发送一个打开它的请求,如果传入的数据库不存在,就发送一个创建并打开它的请求
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; }
可能的错误码
- IDBDatabaseException.UNKNOWN_ERR (1),意外错误,无法归类。
- IDBDatabaseException.NON_TRANSIENT_ERR (2),操作不合法。
- IDBDatabaseException.NOT_FOUND_ERR (3),未发现要操作的数据库。
- IDBDatabaseException.CONSTRAINT_ERR (4),违反了数据库约束。
- IDBDatabaseException.DATA_ERR (5),提供给事务的数据不能满足要求。
- IDBDatabaseException.NOT_ALLOWED_ERR (6),操作不合法。
- IDBDatabaseException.TRANSACTION_INACTIVE_ERR (7),试图重用已完成的事务。
- IDBDatabaseException.ABORT_ERR (8),请求中断,未成功。
- IDBDatabaseException.READ_ONLY_ERR (9),试图在只读模式下写入或修改数据。
- IDBDatabaseException.TIMEOUT_ERR (10),在有效时间内未完成操作。
- IDBDatabaseException.QUOTA_ERR (11),磁盘空间不足
IndexedDB没有版本号,一开始为数据库指定一个版本号,可以调用setVersion()方法,传入以字符串形式表示的版本号
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 complete. Database name: " + database.name + ", Version: " + database.version); }; } else { alert("Database already initialized. Database name: " + database.name + ", Version: " + database.version); }
对象存储空间
如果想验证请求是否成功完成,可以把返回的请求对象保存在一个变量中,然后再指定onerror或onsuccess事件处理程序
//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); }
事务
事务对象本身也有事件处理程序:onerror和oncomplete
transaction.onerror = function(event){ //整个事务都被取消了 }; transaction.oncomplete = function(event){ //整个事务都成功完成了 };
使用游标查询
实例有几个属性
- direction,数值,表示游标移动的方向。默认值为 IDBCursor.NEXT (0),表示下一项。IDBCursor.NEXT_NO_DUPLICATE (1)表示下一个不重复的项, IDBCursor.PREV (2)表示前一项,而 IDBCursor.PREV_NO_DUPLICATE 表示前一个不重复的项。
- key,对象的键。
- value,实际的对象。
- primaryKey,游标使用的键。可能是对象键,也可能是索引键
调用update()方法可以指定的对象更新当前游标的value,与其他操作一样,调用update()方法也会创建一个新的请求
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(){ // 处理成功 }; updateReqeust.onerror = function(){ // 处理失败 }; } } };
调用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(){ // 处理失败 }; } } };
默认情况下,每个游标只发起一次请求,要想发起另一次请求,必须调用下面的方法
- continue(key),移动到结果集中的下一项。参数 key 是可选的,不指定这个参数,游标移动到下一项;指定这个参数,游标会移动到指定键的位置。
- advance(count),向前移动 count 指定的项数。
键范围
先声明一个本地的类型
var IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange;
四种方式
使用only()方法
var onlyRange = IDBKeyRange.only("007");
指定结果集的下界
//从键为"007"的对象开始,然后可以移动到最后 var lowerRange = IDBKeyRange.lowerBound("007"); //如果你想忽略键为 "007" 的对象,从它的下一个对象开始,那么可以传入第二个参数 true //从键为"007"的对象的下一个对象开始,然后可以移动到最后 var lowerRange = IDBKeyRange.lowerBound("007", true);
指定结果集的上界
//从头开始,到键为"ace"的对象为止 var upperRange = IDBKeyRange.upperBound("ace"); //如果你不想包含键为指定值的对象,同样,传入第二个参数 true //从头开始,到键为"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", true, true); //从键为"007"的对象开始,到键为"ace"的对象的上一个对象为止 var boundRange = IDBKeyRange.bound("007", "ace", false, true)
索引
要创建索引,首先引用对象存储空间,然后调用createIndex()方法
var store = db.transaction("users").objectStore("users"), index = store.createIndex("username", "username", { unique: false });
索引上调用openCursor()方法可以创建新的游标,除了将来会把索引键而非主键保存在event.result.key属性中之外,这个游标与在对象存储空间上调用openCursor()返回的游标完全一样
var store = db.transaction("users").objectStore("users"), index = store.index("username"), request = index.openCursor(); request.onsuccess = function(event){ //处理成功 };
索引上也能创建一个特殊的只返回每条记录主键的游标,调用openKeyCursor()方法,这个方法接收的参数与openCursor()相同
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()方法
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,传入createIndex()中的属性路径
- objectStore,索引的对象存储空间
- unique,表示索引键是否唯一的布尔值
下面代码可以知道根据存储的对象建立了哪些索引
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); }
并发问题
刚打开数据库时,要记着指定 onversionchange 事件处理程序。当同一个来源的另一个标签页调
用 setVersion() 时,就会执行这个回调函数。处理这个事件的最佳方式是立即关闭数据库,从而保证
版本更新顺利完成var request, database; request = indexedDB.open("admin"); request.onsuccess = function(event){ database = event.target.result; database.onversionchange = function(){ database.close(); }; }
限制
- IndexedDB数据库只能由同源页面操作,因此不能跨域共享信息
- 每个来源的数据库占用的磁盘空间有限制
- 不允许本地文件访问IndexedDB