第23章 离线应用于客户端存储
1.开发离线 Web 应用需要几个步骤:
- 首先是确保应用知道设备是否能上网,以便下一步执行正确的操作。
- 然后,应用还必须能访问一定的资源(图像、JavaScript、CSS 等),只有这样才能正常工作。
- 最后,必须有一块本地空间用于保存数据,无论能否上网都不妨碍读写。
2.离线检测
(1)navigator.onLine 属性:值为true表示设备能上网,值为false表示设备离线。
(2)online 和 offline事件:当网络从离线变为在线或者从在线变为离线时,分别在 window 对象上触发这两个事件。
3.应用缓存(application cache)或者简称为 appcache,专门为开发离线 Web 应用而设计的。
(1)核心是 applicationCache 对象,有一个 status 属性,值是常量,表示应用缓存的如下当前状态:
- 0:无缓存,即没有与页面相关的应用缓存。
- 1:闲置,即应用缓存未得到更新。
- 2:检查中,即正在下载描述文件并检查更新。
- 3:下载中,即应用缓存正在下载描述文件中指定的资源。
- 4:更新完成,即应用缓存已经更新了资源,而且所有资源都已下载完毕,可以通过 swapCache()来使用了。
(2)还有以下事件,表示其状态的改变:
- checking:在浏览器为应用缓存查找更新时触发。
- error:在检查更新或下载资源期间发生错误时触发。
- noupdate:在检查描述文件发现文件无变化时触发。
- downloading:在开始下载应用缓存资源时触发。
- progress:在文件下载应用缓存的过程中持续不断地触发。
- updateready:在页面新的应用缓存下载完毕且可以通过 swapCache()使用时触发。
- cached:在应用缓存完整可用时触发。
一般来讲,这些事件会随着页面加载按上述顺序依次触发。
4.数据存储
(1)Cookie
1⃣️限制
- cookie 在性质上是绑定在特定的域名下的,只能让批准的接受者访问,而无法被其他 域访问。
- 每个域的 cookie 总数是有限的,不过浏览器之间各有不同,当超过单个域名限制之后还要再设置 cookie,浏览器就会清除以前设置的 cookie。
- 浏览器中对于 cookie 的尺寸也有限制,最好将整个 cookie 长度限制在 4095B(含 4095)以内。
2⃣️cookie 由浏览器保存的以下几块信息构成:每一段信息都作为 Set-Cookie 头的一部分,使用分号加空格分隔每一段
- 名称:一个唯一确定 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。
-
HTTP/1.1 200 OK Content-type: text/html Set-Cookie: name=value; domain=.wrox.com; path=/; secure Other-header: other-header-value
3⃣️JavaScript 中的 cookie
- document.cookie :当用来获取属性值时,返回当前页面可用的所有 cookie 的字符串。当用于设置值的时候,可以设置为一个新的 cookie 字符串,这个字符串会被解释并添加到现有的 cookie 集合中。
- 基本的 cookie 操作有三种:读取、写入和删除,由于 JavaScript 中读写 cookie 不是非常直观,编写一个CookieUtil 对象来简化 cookie 的功能
4⃣️ 子 cookie:为了绕开浏览器的单域名下的 cookie 数限制,子 cookie 存放单个 cookie 中的更小段的数据。
- 最常见的的格式
-
name=name1=value1&name2=value2&name3=value3&name4=value4&name5=value5
一定不要在 cookie 中存储重要和敏感的数据!cookie 数据并非存储在一个安全环境中,其中包含的任何数据都可以被他人访问。
(2)Web Storage :提供一种在 cookie 之外存储会话数据的途径;提供一种存储大量可以跨会话存在的数据的机制。
1⃣️Storage 类型:提供最大的存储空间(因浏览器而异)来存储名值对儿,有如下方法:
- clear()::删除所有值。
- getItem(name):根据指定的名字 name 获取对应的值。
- key(index):获得 index 位置处的值的名字。
- removeItem(name):删除由 name 指定的名值对儿。
- setItem(name, value):为指定的 name 设置一个对应的值。
2⃣️sessionStorage 对象:存储特定于某个会话的数据,存储在 sessionStorage 中的数据只能由最初给对象存储数据的页面访问到。
-
//使用方法存储数据 sessionStorage.setItem("name", "Nicholas"); //使用属性存储数据 sessionStorage.book = "Professional JavaScript"; //使用方法读取数据 var name = sessionStorage.getItem("name"); //使用属性读取数据 var book = sessionStorage.book;
3⃣️globalStorage 对象:跨越会话存储数据,需要指定哪些域可以访问该数据
-
/这个存储空间对于 wrox.com 及其所有子域都是可以 访问的。 //保存数据 globalStorage["wrox.com"].name = "Nicholas"; //获取数据 var name = globalStorage["wrox.com"].name;
4⃣️ localStorage 对象:要访问同一个 localStorage 对象,页面必须来自同一个域名(子域名无效),使用同一种协议,在同一个端口上,相当于 globalStorage[location.host]。
由于 localStorage 是 Storage 的实例,所以可以像使用 sessionStorage 一样来使用它。
//使用方法存储数据
localStorage.setItem("name", "Nicholas");
//使用属性存储数据
localStorage.book = "Professional JavaScript";
//使用方法读取数据
var name = localStorage.getItem("name");
//使用属性读取数据
var book = localStorage.book;
5⃣️对 Storage 对象进行任何修改,都会在文档上触发 storage 事件,事件的 event 对象有以下属性:
- domain:发生变化的存储空间的域名。
- key:设置或者删除的键名。
- newValue:如果是设置值,则是新值;如果是删除键,则是 null。
- oldValue:键被更改之前的值。
(3)IndexedDB:浏览器中保存结构化数据的一种数据库。IndexedDB 最大的特色是使用对象保存数据,个 IndexedDB 数据库,就是 一组位于相同命名空间下的对象的集合。
1⃣️调用 indexDB.open(),如果传入的数据库已经存在,就会打开它;如果传入的数据库还不存在,就会发送一个创建并打开它,返回一个 IDBRequest 对象,在这个对象上可以添加 onerror 和 onsuccess 事件处理程序。默认情况下,IndexedDB 数据库是没有版本号的,最好一开始就为数据库指定一个版本号,调用 setVersion()方法,传入以字符串形式表示的版本号。
-
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; };
2⃣️创建对象存储空间,使用 add()或 put()方法来向其中添加数据,这两个方法都接收一个参数,即要保存的对象,然后这个对象就会被保存到存储空间中,区别在空间中已经包含键值相同的对象时,add()会返回错误,而 put()则会重写原有对象。
-
var user = { username: "007", firstName: "James", lastName: "Bond", password: "foo" }; //第二个参数中的 keyPath 属性,就是空间中将要保存的对象的一个属性,将作为存储空间的键来使用。 var store = db.createObjectStore("users", { keyPath: "username" }); //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); }
3⃣️数据库对象上调用 transaction()方法创建事务来读取或修改数据。
-
//没有参数,则只能通过事务来读取数据库中保存的对象。 var transaction = db.transaction(); //传入要访问的一或多个对象存储空间 var transaction = db.transaction("users"); var transaction = db.transaction(["users", "anotherStore"]); /*传入第二个参数,表示访问模式,用 IDBTransaction 接口定义的如下常量表示: READ_ONLY(0)表示只读,READ_WRITE(1)表示读写,VERSION_CHANGE(2)表示改变。 IE10+和 Firefox 4+实现的是IDBTransaction,但在 Chrome 中则叫 webkitIDBTransaction*/ var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction; var transaction = db.transaction("users", IDBTransaction.READ_WRITE);
4⃣️调用 openCursor()方法可以创建游标,返回一个请求对象。
- IDBCursor:的实例有以下几个属性:
- direction:数值,表示游标移动的方向。默认值为 IDBCursor.NEXT(0),表示下一项。 IDBCursor.NEXT_NO_DUPLICATE(1)表示下一个不重复的项,DBCursor.PREV(2)表示前 一项,而 IDBCursor.PREV_NO_DUPLICATE 表示前一个不重复的项。
- key:对象的键。
- value:实际的对象。
- primaryKey:游标使用的键。可能是对象键,也可能是索引键。
-
var store = db.transaction("users").objectStore("users"), request = store.openCursor(); request.onsuccess = function(event){ var cursor = event.target.result; if (cursor){ //必须要检查 console.log("Key: " + cursor.key + ", Value: " + JSON.stringify(cursor.value)); } }; request.onerror = function(event){ //处理失败 };
5⃣️键范围(key range)为使用游标增添了一些灵活性,由 IDBKeyRange 的实例表示。
-
var IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange; //第一种:only()方法,传入你想要取得的对象的键 //保证只取得键为"007"的对象。 var onlyRange = IDBKeyRange.only("007"); //第二种:指定结果集的下界,下界表示游标开始的位置。 //保证游标从键为"007"的对象开始,然后继续向前移动,直至最后一个对象,可以传入第二个参数 true,忽略键为"007"的对象 var lowerRange = IDBKeyRange.lowerBound("007"); //第三种:指定结果集的上界 //保证游标从头开始,到取得键为"ace"的对象终止,可以传入第二个参数 true,忽略键为"ace"的对象 var upperRange = IDBKeyRange.upperBound("ace"); //第四种定:同时指定上、下界,可以接 收 4 个参数:表示下界的键、表示上界的键、可选的表示是否跳过下界的布尔值和可选的表示是否跳过上界的布尔值。 //从键为"007"的对象的下一个对象开始,到键为"ace"的对象的上一个对象为止 var boundRange = IDBKeyRange.bound("007", "ace", true, true);
-
//在定义键范围之后,把它传给 openCursor()方法,就能得到一个符合相应约束条件的游标。 var store = db.transaction("users").objectStore("users"), range = IDBKeyRange.bound("007", "ace"); request = store.openCursor(range); 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!"); } };