乐趣区

JS学习笔记(第23章)(离线应用与客户端存储)

所谓 Web 离线应用,就是在设备不能上网的情况下仍然可以运行的应用。开发离线 Web 应用需要几个步骤:(1)确保应用知道设备是否能上网;(2)应用还必须能访问一定的资源(图像、JavaScript、CSS 等);(3)必须有一块本地空间用于保存数据,无论能否上网都不妨碍读写。
1、离线检测
HTML5 定义了一个 navigator.onLine 属性,这个属性值为 true 表示设备能上网,值为 false 表示设备离线。单独使用 navigator.onLine 属性不能确定网络是否连通。即便如此,在青丘发生错误的情况下,检测这个属性仍然是管用的。
if(navigator.onLine) {
// 正常工作
} else {
// 执行离线状态时的任务
}
除了 navigator.onLine 属性之外,为了更好地确定网络是否可用,HTML5 还定义了两个事件:onLine 和 offline。当网络从离线变为在线或者从在线变为离线时,分别触发这两个事件。这两个事件在 window 对象上触发。
// 从离线变为在线
EventUtil.addHandler(window, “online”, function() {
alert(“Online”);
});
// 从现在变为离线
EventUtil.addHandler(window, “offline”, function() {
alert(“Offline”);
});
为了检测应用是否离线,在网络加载后,最好先通过 navigator.onLine 取得初始的状态。然后,就是通过上述两个事件来确定网络连接状态是否变化。当上述事件触发时,navigator.onLine 属性的值也会改变。
2、应用缓存
(1)HTML5 的应用缓存,或者简称为 appache,是专门为开发离线 Web 应用而设计的。Appcache 就是从浏览器的缓存中分出来的一块缓存区。要想在这个缓存中保存数据,可使用一个描述文件(manifest file)列出要下载和缓存的资源。
CACHE MANIFEST
#Comment

file.js
file.css
要将描述文件与页面关联起来,可以在 <html> 中的 manifest 属性中指定这个文件的路径,例如
<html manifest=”/offline.manifest”>
以上代码告诉页面,/offline.manifest 中包含着描述文件。
(2)虽然应用缓存的意图是确保离线时资源可用,但也有相应的 JavaScript API 让我们知道它都在做什么,这个 API 的核心是 applicationCache 对象,这个对象有一个 status 属性,属性的值是常量,表示应用缓存的如下当前状态。

0:无缓存,即没有 u 页面相关的应用缓存
1:闲置,即应用缓存未得到更新
2:检查中,即正在下载描述文件并检查更新
3:下载中,即应用缓存正在下载描述文件中的指定资源
4:更新完成,即应用缓存已经更新了资源,而且所有资源都已下载完毕,可以通过 swapCache() 来使用了
5:废弃,即应用缓存的描述文件已经不存在了,因此页面无法再访问缓存。

(3)应用缓存还有很多相关的事件,表示其状态的改变。以下是这些事件:

checking:在浏览器为应用缓存查找更新时触发
error:在检查更新或下载资源其期间发生错误时触发
noupdate:在检查描述文件发现文件无变化时触发
downloading:在开始下载应用缓存资源时触发
progress:在文件下载应用缓存的过程中持续不断的触发
updateready:在页面新的应用缓存下载完毕且可以通过 swapCache() 使用时触发
cached: 在应用缓存完整可用时触发

一般来讲,这些时间会随着页面加载按上述顺序依次触发,不过通过调用 update() 方法也可以手工敢于,让应用缓存为检查更新而触发上述事件。
applicationCache.update();
update() 已经一经调用,应用缓存就会去检查描述文件是否更新(触发 checking 事件),然后就像页面刚刚加载一样,继续执行后续操作。如果触发了 cached 事件,就说明应用缓存已经准备就绪,不会再发生其他操作了。如果触发了 updateready 事件,则说明新版本的应用缓存已经可用,而此时你需要调用 swapCache() 来启用新应用缓存。
Event.addHandler(applicationCache, “updateready”, function() {
applicationCache.swapCache();
});
3、数据存储
3.1 Cookie
HTTP Cookie,通常直接叫做 cookie,最初是在客户端用于存储会话信息的。该标准要求服务器对任意 HTTP 请求发送 Set-Cookies HTTP 头作为响应的一部分,其中包含会话信息。例如,这种服务器响应头可能如下:
HTTP/1.1 200 OK
Content-type: text/html
Set-Cookie: name=value
Other-header: other-header-value
这个 HTTP 响应设置以 name 为名称,以 value 为值得一个 cookie,名称和值在传送时都必须是 URL 编码的。浏览器会存储这样的会话信息,并在这之后,通过为每个请求添加 Cookie HTTP 头将信息发送回服务器。
GET /index.html HTTP/1.1
Cookie: name=value
Other-header: other-header-value
发送回服务器的额外信息可以用于唯一验证客户来自于发送的哪个请求。
1、限制(绑定在特定域名下;数量限制;尺寸限制)(1)cookie 在性质上是绑定在特定的域名下的。当设定了一个 cookie 后,再给创建它的域名发送请求时,都会包含这个 cookie。这个限制确保里存储在 cookie 中的信息只能让批准的接受者访问,而无法被其他域访问。(2)每个域的 cookie 总数总是有限的,当超过单个域名限制之后还要再设置 cookie,浏览器就会清除以前设置的 cookie。IE 和 Opera 会删除最近最少使用过的 cookie,腾出空间给新设置的 cookie。Firefox 看上去好像是随机决定要清除哪个 cookie,所以考虑 cookie 限制非常重要,以免出现不可预期的后果。(3)浏览器中对于 cookie 的尺寸也有限制。尺寸限制影响到一个域下所有的 cookie,而并非每个 cookie 单独限制。如果你尝试创建超过最大尺寸限制的 cookie,那么该 cookie 会被悄无声息地丢掉。2、cookie 的构成 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 格式的日期,用于指定应该删除 cookie 的准确时间。cookie 可在浏览器关闭后依然保存在用户的机器上。如果你设置的失效日期是个以前的时间,则 cookie 会被立即删除。
安全标志:指定后,cookie 只有在使用 SSL 连接的时候才发送到服务器。例如,cookie 信息只能发送给 https://www.wrox.com,而 http://www.wrox.com 的请求则不能发送。

每一段信息都作为 Set-Cookie 头的一部分,使用分号加空格分隔每一段,如下例所示:
HTTP/1.1 200 OK
Content-type: text/html
Set-Cookie: name=value; expires=Mon, 22-Jan-07 07:10:24 GMT; domain=.wrox.com
Other-header: other-header-value
secure 标志是 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
这里创建了一个对于所有 wrox.com 的子域和域名下(由 path 参数指定的)所有页面都是有效的 cookie。因为设置了 secure 标志,这个 cookie 只能通过 SSL 连接才能传输。
尤其要注意,域、路径、失效时间和 secure 标志都是服务器给浏览器的指示,以指定何时应该发送 cookie。这些参数并不会作为发送到服务器的 cookie 信息的一部分,只有名值对儿才会被发送。
3、JavaScript 中的 cookie
JavaScript 中处理 cookie 有些复杂,因为 BOM 的 document.cookie 属性比较独特,它会因为使用它的不同而表现出不同的行为。
当用来获取属性值时,document.cookie 返回当前页面可用的(根据 cookie 的域、路径、失效时间和安全设置)所有 cookie 的字符串,一系列由分号隔开的名值对儿。
当用于设置值的时候,document.cookie 属性可以设置为一个新的 cookie 字符串。这个 cookie 字符串会被解释并添加到现有的 cookie 集合中。设置 document.cookie 并不会覆盖 cookie,除非设置 cookie 的名称已经存在。设置 cookie 的格式如下,和 Set-Cookie 头中使用的格式一样。
name=value; expires=expiration_time; path=domain_name; secure
这些参数中,只有 cookie 的名字和值是必需的。如:最好每次设置 cookie 时都像下面这样使用 encodeURI-Component();
document.cookie = encodeURIComponent(“name”) + “=” + encodeURIComponent(“Nicholas”);
要给被创建的 cookie 指定额外的信息,只要将参数追加到该字符串,和 Set-Cookie 头中的格式一样,如下所示:
document.cookie = encodeURIComponent(“name”) + “=” + encodeURIComponent(“Nicholas”) + “; domain=.wrox.com; path=/”;
由于 JavaScript 中读写 cookie 不是非常直观,常常需要写一些函数来简化 cookie 功能。基本的 cookie 操作有 3 种:读取、写入和删除。
所有名字和值都是经过 URL 编码的,所有必须使用 decodeURIComponent() 来解码
var CookieUtil = {
get: function(name){
// 查找 cookie 名加上等于号的位置。如果找到了,那么使用 indexOf() 查找该位置之后的第一个分号(表示了该 cookie 的结束位置)。如果没有找到分号,则表示该 cookie 是字符串中的最后一个,则余下的字符串都是 cookie 的值。该值使用 decodeURIComponent() 进行解码并最后返回。如果没有发现 cookie,则返回 null。
var cookieName = encodeURIComponent(name) + “=”,
cookieStart = document.cookie.indexOf(cookieName),
cookieValue = null;

if(cookieStart > -1){
var cookieEnd = document.cookie.indexOf(“;”,cookieStart);
if(cookieEnd == -1){
cookieEnd = document.cookie.length;
}
cookieValue = decodeURIComponent(document.cookie.substring(cookieStart+cookieName.length,cookieEnd));
}
return cookieValue;
},

set: function(name, value, expires, path, domain, secure) {
var cookieText = encodeURIComponent(name) + “=” + encodeURIComponent(value);

if(expires instanceof Date) {
cookieText += “; expires=” + expires.toGMTString();
}

if(path) {
cookieText += “; path=” + path;
}

if(domain) {
cookieText += “; domain=” + domain;
}

if(secure) {
cookieText += “; secure”;
}

document.cookie = cookieText;
},
// 没有删除已有 cookie 的直接方法。所以,需要使用相同的路径、域和安全选项再次设置 cookie,并将失效时间设置为过去的时间。
unset: function (name, path, domain, secure) {
this.set(name, “”, new Date(0), path, domain, secure);
}
};
然后就可以像下面这样使用上述方法
// 设置 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”);
4、子 cookie 为了绕开浏览器的单域名下的 cookie 数限制,一些开发人员使用了一种称为子 cookie(subcookie)的概念。子 cookie 是存放在的那个 cookie 中的更小段的数据。也就是使用 cookie 值来存储多个名称值对儿。子 cookie 最常见的格式如下
name=name1=value1&name2=value2&name3=value3&name4=value4&name5=value5
子 cookie 一般也以查询字符串否认格式进行格式化。然后这些值可以使用单个 cookie 进行存储和访问,而非对每个名称 - 值对儿使用不同的 cookie 存储。最后网站或者微博应用程序可以无需达到单域名 cookie 上限也可以存储更加结构化的数据。
为了更好地操作子 cookie,必须建立一系列新的方法。子 cookie 的解析序列和序列化会因子 cookie 的期望用途而略有不同并更加复杂些。
获取子 cookie 的方法有两个:get() 和 getAll()。其中 get() 获取单个子 cookie 的值,getAll() 获取所有子 cookie 并将它们放入一个对象中返回,对象的属性为子 cookie 的名称,对应值为子 cookie 对应的值。get() 方法接收两个参数:cookie 的名字和子 cookie 的名字。它其实就是调用 getAll() 获取所有的子 cookie,然后只返回所需的那一个(如果 cookie 不存在则返回 null)。
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(). 为了在同一个 cookie 中存储多个子 cookie,路径、域和 secure 标志必须一致;针对整个 cookie 的失效日期则可以在任何一个单独的子 cookie 写入的时候同时设置。在这个 set() 方法中,第一步是获取指定 cookie 名称对应的所有子 cookie。逻辑或操作符 ”||” 用于当 getAll() 返回 null 时将 subcookies 设置为一个新对象。然后,在 subcookies 对象上设置好子 cookie 值并传给 setAll()。
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 (subcookieParts.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 中的所有子 cookie,然后再将余下的子 cookie 的值保存为 cookie 的值。
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);
}
整个 SubCookieUtil 部分代码如下:
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;
},

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 (subcookieParts.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;

},

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);
}

};
可以像下面这样使用上述方法:
// 取得全部子 cookie
var data = SubCookieUtil.getAll(“data”);
alert(data.name); //”Nicholas”
alert(data.book); //”Professional JavaScript”

// 逐个获取子 cookie
alert(SubCookieUtil.get(“data”,”name”)); //”Nicholas”
alert(SubCookieUtil.get(“data”,”book”)); //”Professional JavaScript”

// 设置两个 cookie
SubCookieUtil.set(“data”, “name”, “Nicholas”);
SubCookieUtil.set(“data”, “book”, “Professional JavaScript”);

// 设置全部子 cookie 和失效日期
SubCookieUtil.setAll(“data”,{name:”Nicholas”, book:”Professional JavaScript”},new Date(“January 1, 2010”));
// 修改名字的值,并修改 cookie 的失效日期
SubCookieUtil.set(“data”, “name”, “Michael”, new Date(“February 1,2010″));
5、关于 cookie 的思考还有一类 cookie 被称为“HTTP 专有 cookie”。HTTP 专有 cookie 可以从浏览器或者服务器设置,但是只能从服务器端读取。由于所有的 cookie 都会由浏览器作为请求头发送,所以在 cookie 中存储大量信息会影响到特定域请求性能。cookie 信息越大,完成对服务器请求的时间也就越长。尽管浏览器对 cookie 进行了大小限制,不过最好还是尽可能在 cookie 中少储存信息,以避免影响性能。
cookie 的性质和它的局限使得并不能作为存储大量信息的理想手段。一定不要在 cookie 中存储重要和敏感的数据。
3.2 IE 用户数据
微软通过一个自定义行为引入了持久化用户数据的概念。要使用持久化用户数据,首先必须如下所示,使用 CSS 在某个元素上指定 userData 行为:
<div style=”behavior:url(#default#userDate)” id=”dataStore”></div>
一旦该元素使用了 userDate 行为,那么就可以(1)使用 setAttribute() 方法在上面保存数据了。为了将数据提交到浏览器缓存中,还必须(2)调用 save() 方法并告诉它要保存到的数据空间的名字。数据空间名字可以完全任意,仅用于区分不同数据集。(3)下一次页面载入之后,可以使用 load() 方法指定同样的数据空间名称来获取数据。
var dataStore = document.getElementById(“dataStore”);
dataStore.setAttribute(“name”, “Nicholas”);
dataStore.setAttribute(“book”,”Professional JavaScript”);
datastore.save(“BookInfo”); // 指定了数据空间的名称为 BookInfo
dataStore.load(“BookInfo”);
alert(dataStore.getAttribute(“name”)); //”Nicholas”
alert(dataStore.getAttribute(“book”)); //”Professional JavaScript”
对 load() 的调用获取了 BookInfo 数据空间中的所有信息,并且是数据可以通过元素访问;只有到载入确切完成后数据方能使用。如果 getAttribute() 调用了不存在的名称或者是尚未载入的名称,则返回 null。
通过 removeAttribute() 方法明确指定要删除某元素数据,只要指定属性名称。删除之后,必须像下面这样再次调用 save() 来提交更改。
dataStore.removeAttribute(“name”);
dataStore.removeAttribute(“book”);
dataStore.save(“BookInfo”);
和 cookie 一样,IE 用户数据并非安全的,所以不能存放敏感信息。
3.3 Web 存储机制
3.4 IndexedDB
4、小结

退出移动版