客户端存储


客户端存储

Web应用允许使用浏览器提供的API实现将数据存储到用户的电脑上。这种客户端存储相当于赋予了Web浏览器记忆功能。比方说,Web应用就可以用这种方式来“记住”用户的偏好甚至是用户所有的状态信息,以便准确地“回忆”起用户上一次访问的位置。客户端存储遵循“同源策略”,因此不同站点的页面是无法互相读取对方存储的数据, 而同一站点的不同页面之间是可以互相共享存储数据的,它为我们提供了一种通信机制,例如,一个页面上填写的表单数据可以显示在另外一个页面中。Web应用可以选择它们存储数据的有效期:比如釆用临时存储可以让数据保存至当前窗口关闭或者浏览器退出;采用永久存储,可以将数据永久地存储到硬盘上,数年或者数月不失效。

客户端存储有以下几种形式:

Web存储

Web存储最初作为HTML5的一部分被定义成API形式,但是后来被剥离出来作为独立的一份标准了。该标准目前还在草案阶段,但其中一部分内容已经被包括IE8在内的所有主流浏览器(可交互地)实现了。Web存储标准所描述的API包含localStorage对象和sessionStorage对象,这两个对象实际上是持久化关联数组,是名值对的映射表,“名”和“值”'都是字符串。Web存储易于使用、支持大容量(但非无限量)数据存储同时兼容当前所有主流浏览器,但是不兼容早期浏览器。

cookie是一种早期的客户端存储机制,起初是针对服务器端脚本设计使用的。尽管在客户端提供了非常繁琐的JavaScript API来操作cookie,但它们难用至极,而且只适合存储少量文本数据。不仅如此,任何以cookie形式存储的数据,不论服务器端是否需要,每一次HTTP请求都会把这些数据传输到服务器端。cookie目前仍然被客户端程序员大量使用的一个重要原因是:所有新旧浏览器都支持它。但是,随着Web Storage的普及,cookie终将会回归到最初的形态:作为一种被服务端脚本使用的客户端存储机制。

IE User Data

微软在IE5及之后的IE浏览器中实现了它专属的客户端存储机制——“userData”。userData可以实现一定量的字符串数据存储,对于IE8以前的IE浏览器中,可以将其用做是Web存储的替代方案。

离线响应用

HTML5标准中定义了一组“离线Web应用” API,用以缓存Web页面以及相关资源(脚本、CSS文件、图像等)。它实现的是将Web应用整体存储在客户端,而不仅仅是存储数据。它能够让Web应用“安装”在客户端,这样一来,哪怕网络不可用的时候Web应用依然是可用的。

Web数据库

为了能够让开发者像使用数据库那样来操作大量数据,很多主流的浏览器纷纷在其中开始集成客户端数据库的功能。Safari、Chrome和Opera都内置了SQL数据库的客户端API。遗憾的是,这类API的标准化工作以失败告终,并且Firefox和IE看样子也都不打算实现这种API。目前还有一种正在标准化的数据库API,称为“索引数据库API” (Indexed Database API)。调用该API返回的是一个不包含査询语言的简单数据库对象。这两种客户端数据库API都是异步的,都使用了事件处理机制(类似D0M爭件机制),这样的方式多多少少会显得有些复杂。

文件系统API

现在主流浏览器都支持一个文件对象,用以将择的文件通过XMLHttpRequest上传到服务端。与之相关的规范(草案阶段)定义了一组API,用于操作一个私有的本地文件系统。在该文件系统中可以进行对文件的读写操作。这些内容正在紧锣密鼓标准化当中,随着这些API被广 泛地实现和支持,Web应用可以使用类似基于文件的存储机制,这对于大部分程序员来说再熟悉不过了。


存储、安全和隐私

Web浏览器通常会提供“记住Web密码”功能,这些密码会以加密的形式安全地存储到硬盘上。任何形式的客户端数据存储都不牵涉加密:任何存储在用户硬盘上的数据都是未加密的形式。这样一来,对于拥有电脑访问权限的恶意用户以及计算机上存在的恶意软件(比如:间谍软件)同样也可以获取到存储的数据。因此,任何形式的客户端存储不应该用来保存密码、商业账号或者其他类似的敏感信息。记住:尽管用户访问你的网站时,愿意在表单中输入一些信息,但绝不代表用户愿意将这些信息保存到硬盘上。就拿信用卡卡号来举例好了,这是用户的隐私,用户并不愿意公开,如果你利用客户端持久性将该信息存储起来,这无异于你将信用卡号写在一张便签纸上,随后粘贴在用户的键盘上,让所有人都看到。

还有要谨记的一点:很多Web用户不信任那些使用cookie和其他客户端存储机制来做类似“跟踪”功能的网站。存储机制来为网站提升用户体验;而不是用它们来收集和侵犯隐私相关的数据。如果网站滥用客户端存储,用户将会禁用该功能,这样一来不仅起不到效果,还会导致依赖客户端存储的网站完全不可用。


localStorage和sessionStorage

实现了“Web存储”草案标准的浏览器在Window对象上定义了两个属性:localStorage和sessionStorage。这两个属性都代表同一个Storage对象——一个持久化关联数组,数组使用字符串来索引,存储的值也都是字符串形式的。Storage对象在使用上和一般的JavaScript对象没什么区别:设置对象的属性为字符串值,随后浏览器会将该值存储起 来。localStorage和sessionStorage两者的区别在于存储的有效期和作用域的不同:数据可以存储多长时间以及谁拥有数据的访问权。

下面,我们会对存储的有效期和作用域进行详细的解释。不过,在此之前,让我们先来看些例子。下面的代码使用的是localStorage,但是它对sessionStorage也同样适用:

var name = localStorage.username;            // 查询一个存储的值
name = localStorage["username"];             // 等价于数组表示法
if (!name) {
    name = prompt("What is your name?");     // 询问用户一个问题
    localStorage.username = name;            // 存储用户的答案
}
// 迭代所有存储的name/value对 
for(var name in localStorage) {              // 迭代所有存储的名字
    var value = localStorage[name];          // 查询每个名字对应的值

Storage对象还定义了一些诸如存储、获取、遍历和删除的方法。

“Web存储”草案标准指出,我们既可以存储结构化的数据(对象和数组),也可以存储原始类型数据,还可以存储诸如日期、正则表达式甚至文件对象在内的内置类型的数据。如果想要存储和获取其他类型的数据,不得不自己手动进行编码和解码。如以下例子所示:

// 当存储一个数字的时候,会把它自动转换成一个字符串
// 但是,当获取该值的时候别忘记了手动将其转换成数字类型
localStorage.x = 10;
var x = parseInt(localStorage.x);

// 同样地,存储一个日期类型数据的时候进行编码,获取的时候进行解码
localStorage.lastRead = (new Date()).toUTCString();
var lastRead = new Date(Date.parse(localStorage.lastRead));

// 使用JSON可以使得对基本数据类型编码的工作变得很方便
localStorage.data = JSON.stringify(data);         // 编码然后存储
var data = JSON.parse(localstorage.data);         // 获取数值之后再解码

存储有效期和作用域

localStorage和sessionStorage的区别在于存储的有效期和作用域的不同。通过localStorage存储的数据是永久性的,除非Web应用刻意删除存储的数据,或者用户通过设置浏览器配置(浏览器提供的特定UI)来删除,否则数据将一直保留在用户的电脑上,永不过期。

localstorage的作用域是限定在文档源(document origin)级别的。文档源是通过协议、主机名以及端口三者来确定的,因此,下面每个URL都拥有不同的文档源:

http://www.example.com          // 协议:http;主机名:www.example.com
https://www.example.com         // 不同协议
http://static.example.com       // 不同主机名
http://www.example.com:8000     // 不同端口

同源的文档间共享同样的localStorage数据(不论该源的脚本是否真正地访问localStorage)。它们可以互相读取对方的数据,甚至可以覆盖对方的数据。但是,非同源的文档间互相都不能读取或者覆盖对方的数据(即使它们运行的脚本是来自同一台第三方服务器也不行)。

需要注意的是localStorage的作用域也受浏览器供应商限制。如果你使用Firefox访问站点,那么下次用另一个浏览器(比如,Chrome)再次访问的时候,那么本次是无法获取上次存储的数据的。

通过sessionStorage存储的数据和通过localStorage存储的数据的有效期也是不同的:前者的有效期和存储数据的脚本所在的最顶层的窗口或者是浏览器标签页是一样的。一旦窗口或者标签页被永久关闭了,那么所有通过sessionStorage存储的数据也都被删除了。(当时要注意的是,现代浏览器已经具备了重新打开最近关闭的标签页随后恢复上一次浏览的会话功能,因此,这些标签页以及与之相关的sessionStorage的有效期可能会更加长些)。

与localStorage—样,sessionStorage的作用域也是限定在文档源中,因此非同源文档间都是无法共享sessionStorage的。不仅如此,sessionStorage的作用域还被限定在窗口中。如果同源的文档渲染在不同的浏览器标签页中,那么它们互相之间拥有的是各自的sessionStorage数据,无法共享,一个标签页中的脚本是无法读取或者覆盖由另一个标签页脚本写入的数据,哪怕这两个标签页渲染的是同一个页面,运行的是同一个脚本也不行。

要注意的是:这里提到的基于窗口作用域的sessionStorage指的窗口只是顶级窗口。如果一个浏览器标签页包含两个