离线应用和客户端存储

JavaScript 2020-08-05 846

1.离线检测

HTML5定义了一个navigator.onLine属性,这个属性值为 true表示设备能上网,值为 false 表示设备离线。这个属性的关键是浏览器必须知道设备能否访问网络,从而返回正确的值。实际应用中,navigator.onLine 在不同浏览器间还有些小的差异。
 IE6+和 Safari 5+能够正确检测到网络已断开,并将 navigator.onLine 的值转换为 false。
 Firefox 3+和 Opera 10.6+支持 navigator.onLine 属性,但你必须手工选中菜单项“文件 → Web开发人员(设置)→ 脱机工作”才能让浏览器正常工作。
 Chrome 11及之前版本始终将navigator.onLine 属性设置为 true。(此bug2011年被修复,相信没有pc还在使用旧版本的浏览器)
由于存在上述兼容性问题,单独使用 navigator.onLine 属性不能确定网络是否连通。即便如此, 在请求发生错误的情况下,检测这个属性仍然是管用的。以下是检测该属性状态的示例。
2020年大部分浏览器都是支持该属性的。且很多浏览器都转入了webkit的怀抱。

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 属性的值也会改变,不过必须要手工轮询这个属性才能检测到网络状态的变化。

支持离线检测的浏览器有 IE 6+(只支持 navigator.onLine 属性)、Firefox 3、Safari 4、Opera 10.6、 Chrome、iOS 3.2 版 Safari 和 Android 版 WebKit。

2.数据存储

2.1 cookie
  1. 限制
    cookie 在性质上是绑定在特定的域名下的。当设定了一个 cookie后,再给创建它的域名发送请求时,都会包含这个 cookie。这个限制确保了储存在 cookie 中的信息只能让批准的接受者访问,而无法被其他域访问。
    由于 cookie 是存在客户端计算机上的,还加入了一些限制确保 cookie 不会被恶意使用,同时不会占据太多磁盘空间。每个域的 cookie 总数是有限的,不过浏览器之间各有不同。如下所示。
     IE6 以及更低版本限制每个域名最多 20 个 cookie。
     IE7 和之后版本每个域名最多 50 个。IE7 最初是支持每个域名最大 20 个 cookie,之后被微软的一个补丁所更新。
     Firefox 限制每个域最多 50 个 cookie。
     Opera 限制每个域最多 30 个 cookie。
     Safari 和 Chrome 对于每个域的 cookie 数量限制没有硬性规定。
    当超过单个域名限制之后还要再设置cookie,浏览器就会清除以前设置的 cookie。IE和Opera会删 除最近最少使用过的(LRU,Least Recently Used)cookie,腾出空间给新设置的 cookie。Firefox 看上去好像是随机决定要清除哪个 cookie,所以考虑 cookie 限制非常重要,以免出现不可预期的后果。
    浏览器中对于 cookie 的尺寸也有限制。大多数浏览器都有大约4096B(加减 1)的长度限制。为了 最佳的浏览器兼容性,最好将整个 cookie 长度限制在 4095B(含 4095)以内。尺寸限制影响到一个域 下所有的 cookie,而并非每个 cookie 单独限制。
    如果你尝试创建超过最大尺寸限制的 cookie,那么该 cookie 会被悄无声息地丢掉。注意,虽然一个字符通常占用一字节,但是多字节情况则有不同。
2. cookie 的构成

cookie 由浏览器保存的以下几块信息构成。
 名称:一个唯一确定 cookie 的名称。cookie 名称是不区分大小写的,所以 myCookie 和 MyCookie 被认为是同一个 cookie。然而,实践中最好将 cookie 名称看作是区分大小写的,因为某些服务器会这样处理 cookie。cookie 的名称必须是经过 URL 编码的。
 值:储存在 cookie 中的字符串值。值必须被 URL 编码。
 域:cookie 对于哪个域是有效的。所有向该域发送的请求中都会包含这个 cookie 信息。这个值可以包含子域(subdomain,如http://www.wrox.com),也可以不包含它(如.http://wrox.com,则对于http://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。

3. JavaScript 中的cookie

cookie在获取时所有名字和值都是经过URL编码的,所以必须使用 decodeURIComponent()来解码。
设置cookie时,使用document.cookie,示例:

document.cookie = "name=Nicholas";  

这段代码创建了一个叫 name的cookie,值为Nicholas。当客户端每次向服务器端发送请求的时候,都会发送这个cookie;当浏览器关闭的时候,它就会被删除。虽然这段代码没问题,但因为这里正好名称和值都无需编码,所以最好每次设置cookie 时都像下面这个例子中一样使用encodeURIComponent()。

document.cookie = encodeURIComponent("name") + "=" +encodeURIComponent("Nicholas");  

要给被创建的 cookie 指定额外的信息,只要将参数追加到该字符串,和 Set-Cookie 头中的格式一样,如下所示。

document.cookie = encodeURIComponent("name") + "=" +encodeURIComponent("Nicholas") + "; domain=.wrox.com; path=/";  

对cookie进行操作

var CookieUtil = {  
    // 获取cookie的值  
    get: function (name) {  
        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;  
    },  
    // 设置cookie的值  
    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  
    unset: function (name, path, domain, secure) {  
        this.set(name, "", new Date(0), path, domain, secure);  
    }  
};  
  1. 子cookie
    为了绕开浏览器的单域名下的 cookie 数限制,一些开发人员使用了一种称为子 cookie(subcookie) 的概念。子 cookie 是存放在单个 cookie 中的更小段的数据。也就是使用 cookie 值来存储多个名称值对儿。子 cookie 最常见的的格式如下所示。
name=name1=value1&name2=value2&name3=value3&name4=value4&name5=value5  

子cookie一般也以查询字符串的格式进行格式化。然后这些值可以使用单个 cookie 进行存储和访问,而非对每个名称值对儿使用不同的 cookie 存储。最后网站或者 Web 应用程序可以无需达到单域名cookie 上限也可以存储更加结构化的数据。
为了更好地操作子 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;  
    },  
    //省略了更多代码  
};  

这里的 set()方法接收 7 个参数:cookie 名称、子 cookie 名称、子 cookie 值、可选的 cookie 失效日期或时间的 Date 对象、可选的 cookie 路径、可选的 cookie 域和可选的布尔 secure 标志。所有的可 选参数都是作用于cookie本身而非子cookie。为了在同一个cookie中存储多个子cookie,路径、域和secure 标志必须一致;针对整个 cookie 的失效日期则可以在任何一个单独的子 cookie 写入的时候同时设置。在 这个方法中,第一步是获取指定 cookie 名称对应的所有子 cookie。逻辑或操作符“||”用于当 getAll()返回 null 时将 subcookies 设置为一个新对象。然后,在 subcookies 对象上设置好子 cookie 值并传给 setAll()。

而 setAll()方法接收 6 个参数:cookie 名称、包含所有子 cookie 的对象以及和 set()中一样的 4个可选参数。这个方法使用 for-in 循环遍历第二个参数中的属性。为了确保确实是要保存的数据,使用了 hasOwnProperty()方法,来确保只有实例属性被序列化到子 cookie 中。由于可能会存在属性名为空字符串的情况,所以在把属性名加入结果对象之前还要检查一下属性名的长度。将每个子 cookie的名值对儿都存入 subcookieParts 数组中,以便稍后可以使用 join()方法以&号组合起来。剩下的方法则和 CookieUtil.set()一样。
可以按如下方式使用这些方法。

//假设 document.cookie=data=name=Nicholas&book=Professional%20JavaScript  
//设置两个 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"));  

子 cookie的最后一组方法是用于删除子 cookie 的。普通 cookie 可以通过将失效时间设置为过去的时间的方法来删除,但是子 cookie 不能这样做。为了删除一个子 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);  
    }  
};  

这里定义的两个方法用于两种不同的目的。unset()方法用于删除某个 cookie 中的单个子 cookie 而不影响其他的;而 unsetAll()方法则等同于 CookieUtil.unset(),用于删除整个 cookie。和 set()及 setAll()一样,路径、域和 secure 标志必须和之前创建的 cookie 包含的内容一致。这两个方法可以像下面这样使用。

//仅删除名为 name 的子 cookie  
SubCookieUtil.unset("data", "name");  
//删除整个 cookie  
SubCookieUtil.unsetAll("data");  

如果你担心开发中可能会达到单域名的 cookie 上限,那么子 cookie 可是一个非常有吸引力的备选方案。不过,你需要更加密切关注 cookie 的长度,以防超过单个 cookie 的长度限制。

3.3.3 Web存储机制

  1. Storage 类型

Storage 类型提供最大的存储空间(因浏览器而异)来存储名值对儿。Storage 的实例与其他对象类似,有如下方法。
 clear(): 删除所有值;Firefox 中没有实现 。
 getItem(name):根据指定的名字 name 获取对应的值。
 key(index):获得 index 位置处的值的名字。
 removeItem(name):删除由 name 指定的名值对儿。
 setItem(name, value):为指定的 name 设置一个对应的值。

其中,getItem()、removeItem()和 setItem()方法可以直接调用,也可通过 Storage 对象间接调用。因为每个项目都是作为属性存储在该对象上的,所以可以通过点语法或者方括号语法访问属性来读取值,设置也一样,或者通过delete 操作符进行删除。不过,我们还建议读者使用方法而不是属性来访问数据,以免某个键会意外重写该对象上已经存在的成员。
可以使用 length 属性来判断有多少名值对儿存放在 Storage 对象中。但无法判断对象中所有数据的大小,不过 IE8 提供了一个 remainingSpace 属性,用于获取还可以使用的存储空间的字节数。
Storage类型只能存储字符串。

1.sessionStorage 对象

sessionStorage 对象存储特定于某个会话的数据,也就是该数据只保持到浏览器关闭。这个对象就像会话 cookie,也会在浏览器关闭后消失。存储在 sessionStorage 中的数据可以跨越页面刷新而存在,同时如果浏览器支持,浏览器崩溃并重启之后依然可用(Firefox 和 WebKit 都支持,IE 则不行)。
因为 seesionStorage 对象绑定于某个服务器会话,所以当文件在本地运行的时候是不可用的。存储在 sessionStorage 中的数据只能由最初给对象存储数据的页面访问到,所以对多页面应用有限制。
由于 sessionStorage 对象其实是 Storage 的一个实例,所以可以使用 setItem()或者直接设置新的属性来存储数据。下面是这两种方法的例子。

//使用方法存储数据  
sessionStorage.setItem("name", "Nicholas");  
//使用属性存储数据  
sessionStorage.book = "Professional JavaScript";  

不同浏览器写入数据方面略有不同。Firefox 和 WebKit 实现了同步写入,所以添加到存储空间中的数据是立刻被提交的。而 IE 的实现则是异步写入数据,所以在设置数据和将数据实际写入磁盘之间可能有一些延迟。对于少量数据而言,这个差异是可以忽略的。对于大量数据,你会发现 IE 要比其他浏览器更快地恢复执行,因为它会跳过实际的磁盘写入过程。
在 IE8 中可以强制把数据写入磁盘:在设置新数据之前使用 begin()方法,并且在所有设置完成之后调用 commit()方法。
看以下例子。

//只适用于 IE8  
sessionStorage.begin();  
sessionStorage.name = "Nicholas";  
sessionStorage.book = "Professional JavaScript";  
sessionStorage.commit();  

这段代码确保了name 和book的值在调用commit()之后立刻被写入磁盘。调用 begin()是为了确保在这段代码执行的时候不会发生其他磁盘写入操作。对于少量数据而言,这个过程不是必需的;不过,对于大量数据(如文档之类的)可能就要考虑这种事务形式的方法了。 sessionStorage 中有数据时,可以使用 getItem()或者通过直接访问属性名来获取数据。两种 方法的例子如下。

2.localStorage 对象

要访问同一个 localStorage 对象,页面必须来自同一个域名(子域名无效),使用同一种协议,在同一个端口上。

//使用方法存储数据  
localStorage.setItem("name", "Nicholas");  
//使用属性存储数据  
localStorage.book = "Professional JavaScript";  
//使用方法读取数据  
var name = localStorage.getItem("name");  
//使用属性读取数据  
var book = localStorage.book;  

存储在localStorage 中的数据,数据保留到通过 JavaScript 删除或者是用户清除浏览器缓存。

3.限制

与其他客户端数据存储方案类似,Web Storage 同样也有限制。这些限制因浏览器而异。一般来说, 对存储空间大小的限制都是以每个来源(协议、域和端口)为单位的。换句话说,每个来源都有固定大小的空间用于保存自己的数据。考虑到这个限制,就要注意分析和控制每个来源中有多少页面需要保存数据。
对于 localStorage 而言,大多数桌面浏览器会设置每个来源 5MB 的限制。Chrome 和 Safari 对每 个来源的限制是 2.5MB。而 iOS 版 Safari 和 Android 版 WebKit 的限制也是 2.5MB。 对 sessionStorage 的限制也是因浏览器而异。有的浏览器对 sessionStorage 的大小没有限制, 但 Chrome、Safari、iOS 版 Safari 和 Android 版 WebKit 都有限制,也都是 2.5MB。IE8+和 Opera 对 sessionStorage 的限制是 5MB。

IndexDB

参考:http://www.ruanyifeng.com/blog/2018/07/indexeddb.html

标签:JavaScript

文章评论

评论列表

已有0条评论