Dive Into HTML5:本地存储

本地持久化存储一直是本地客户端程序优于 web 程序的一个方面。对于本地应用程序,操作系统会一共一个抽象层,用于存储和获取特定于应用程序的数据,例如用户设置或者运行时状态。这些值可以被存储于注册表、INI 文件,或者其他什么地方,这取决于操作系统的实现。如果你的本地应用程序需要不简单是键值对形式的本地存储,你也可以使用嵌入式数据库、发明你自己的文件格式,或者其他很多种解决方案。

然而,web 应用程序就没这些优点了。于是在 web 早期我们就发明了 cookie,目的是在本地持久存储少量数据。但是,cookie 有三个致命缺点:

  • cookie 会包含进每一个 HTTP 请求,因此会减慢 web 应用程序,产生不必要的重复数据;
  • cookie 会包含进每一个 HTTP 请求,因此网络上发送的数据就不能加密(除非你的整个应用都是用的 SSL)
  • cookie 限制数据大小为 4 KB——这已经足以降低你的应用程序的速度,但是 4 KB 的大小有时候确实会捉襟见肘。

我们真正想要的是:

  • 更大的存储空间
  • 在客户端上的
  • 不受页面刷新的影响
  • 不需要提交到服务器

在 HTML5 之前,我们没有任何办法能够同时满足以上要求。

HTML5 之前的解决方案

一开始,我们的浏览器只有 Internet Explorer。或者说,那是 Microsoft 所期望的世界。到后来,在第一次浏览器大战期间,Microsoft 发明了一大堆东西,全部包含进了 Internet Explorer,而这个浏览器赢得了第一次浏览器大战。这其中之一就是 DHTML 行为(DHTML Behaviors),其中有一个行为叫做“用户数据 userData”。

userData 允许每一个域名的页面保存64KB数据,包括有层次结构的基于 XML 的结构。(受信域名,例如 intranet 站点,可以存储10倍于这个数字,也就是640KB。这几乎对任何人都已经足够了。)IE 不会有任何授权对话框,也不能增加额外的存储空间。

2002年,Adobe 在 Flash 6 引入了一项新的特性,却有一个明显会误导的名字:Flash cookie。在 Flash 环境下,这个特性被称为 Local Shared Object。简单来说,它允许每个域名的 Flash 对象存储100KB数据。Brad Neuberg 开发了一个 Flash-JavaScript 桥的早期原型,称为 AMASS(AJAX Massive Storage System),但它受限于 Flash 的设计怪癖。直到2006年,Flash 8 提供了 ExternalInterface,这么一来就可以由 JavaScript 直接访问 LSO,因此访问速度提升了一个数量级,变得更简单更快速。Brad 重写了 AMASS,并将其集成在 Dojo Toolkit 的 dojox.storage 中。Flash 给每个域名100KB的自由空间。如果超出,则允许按照数量级增加(1Mb、10Mb等等)。

2007年,Google 提供了 Gears,一个开源的浏览器插件,旨在为浏览器提供额外的功能。(我们曾经在为 IE 提供地理位置API的时候介绍过 Gears。)Gears 提供了一组基于 SQLite 的嵌入式数据库的 API。只要用户一次授权,Gears 能够按照域名在 SQL 数据库表中存储无限大小的数据。

与此同时,Brad Neuberg 和其他人继续 dojox.storage 的开发,以提供一种对这类插件和 API 的统一的接口。2009年,dojox.storage 能够自动检测(在其上层提供一种统一的接口)Adobe Flash、Gears、Adobe AIR 和 HTML5 存储的早期原型(仅有 Firefox 的一个较老版本实现)。

正如你所看到的这些解决方案,它们都有或多或少的问题:不是特定于某一浏览器,就是需要安装第三方插件。我们还需要对不同之处做一种“屏蔽”(正如 dojox.storage 所做的那样),它们有着不同的接口、不同的存储限制、不同的用户体验。这就是 HTML5 所要解决的问题:提供一种标准的 API,由多种浏览器提供原生支持,不需要安装第三方插件。

HTML5 存储简介

我所说的“HTML5 存储(HTML5 Storage)”,也就是标准上说的 Web Storage,这曾经是 HTML5 标准的一部分,后来由于某些不和谐的政治因素从 HTML5 分离,成为一个独立的标准。有些浏览器厂商也称为“本地存储 Local Storage” 或者 “DOM 存储 DOM Storage”。由于一些相关的问题、相似的名字、标准的合并问题等等,这个名字变得相当复杂,我们会再和后面的内容中仔细讲解。

那么,什么是 HTML5 Storage 呢?简单来说,就是一种让 web 页面能够以键值对的形式,在客户端web浏览器中将数据存储在本地的方法。就像 cookie 一样,这种数据在你离开 web 站点、关闭标签页、退出浏览器等等的时候依然保存。不同于 cookie 的地方是,这个数据不会被发送到远程 web 服务器(除非你自己手动发送)。另外,不同于我们前面所说的那些解决方案,这种机制是 web 浏览器原生提供的,所以不需要第三方插件的支持。

那么,什么浏览器才支持呢?目前,几乎所有主流浏览器的最新版本都支持 HTML5 Storage 了,甚至包括 Internet Explorer!

IEFirefoxSafariChromeOperaiPhoneAndroid
8.0+3.5+4.0+4.0+10.5+2.0+2.0+

我们可以使用 JavaScript 来访问 HTML5 Storage,通过全局的window对象的localStorage对象。在我们使用之前,我们需要首先检测是否可用:

function supports_html5_storage() {
     try {
         return 'localStorage' in window && window['localStorage'] !== null;
    } catch (e) {
        return false;
     }
}

当然,我们也可以使用 Modernizr 检测:

if (Modernizr.localstorage) {
    // window.localStorage is available!
} else {
    // no native support for HTML5 storage 🙁
    // maybe try dojox.storage or a third-party solution
}

使用 HTML5 Storage

HTML5 Storage 基于键值对存储。你要存储的数据需要有一个名字作为键,然后你就可以使用这个键读取这个数据。这个键是一个字符串;数据则可以是 JavaScript 支持的任何数据类型,包括字符串、布尔值、整数和浮点数。但是,我们通常将数据作为字符串进行存储。如果你存储读取非字符串数据,你就得使用类似parseInt()或者parseFloat()这样的函数,将读取的数字转换成所需要的 JavaScript 数据类型。

interface Storage {
    getter any getItem(in DOMString key);
    setter creator void setItem(in DOMString key, in any data);
};

调用setItem()时需要有一个键作为参数。如果这个键已经存在,则原有的值将被覆盖。getItem()也要有一个键作为参数,如果该键不存在,则会直接返回null,不会引发异常。

正如其他的 JavaScript 对象,你可以将localStorage对象作为一个关联数组。除了使用getItem()setItem()函数,也可以直接使用方括号语法。例如:

var foo = localStorage.getItem("bar");
// ...
localStorage.setItem("bar", foo);

也可以写成:

var foo = localStorage["bar"];
// ...
localStorage["bar"] = foo;

当然也有函数,用于删除已有的值,清空整个存储区域(也就是一次性将所有键和值全部删除)等。

interface Storage {
    deleter void removeItem(in DOMString key);
    void clear();
};

调用removeItem()时传入一个不存在的键不会做任何动作。

最后,有一个属性可以获得存储区域中值的总数,借助另外一个函数则可以使用索引进行遍历(获取每个键的名字)。

interface Storage {
    readonly attribute unsigned long length;
    getter DOMString key(in unsigned long index);
};

调用key()的索引如果不在 0 – (length-1) 之间,函数将返回null

HTML5 Storage 变化追踪

如果你需要以编程方式跟踪存储区域的改变,你需要使用storage事件。storage事件在window对象调用setItem()removeItem()或者clear(),并且的确有东西改变的时候会被派发。例如,如果你设置一个已存在的值,或者在一个空的存储区域调用clear()函数,因为没有任何动作,所以就不会触发storage事件。

只要支持localStorage对象的地方都会支持 storage 事件,包括 IE8。IE 8 不支持 W3C 的标准的 addEventListener(事实上这一函数在 IE 9 才被加入)。因此,为了监听 storage 事件,你需要检测浏览器支持哪种事件机制(如果你了解这个内容,就可以跳过这部分了。处理 storage 事件同其他时间一样。如果你使用 jQuery 或者其他 JavaScript 库的事件处理函数,那么你也可以这么处理 storage 事件。)

if (window.addEventListener) {
    window.addEventListener("storage", handle_storage, false);
} else {
    window.attachEvent("onstorage", handle_storage);
};

handle_storage回调函数接受一个StorageEvent参数。在 Internet Explorer 中,event对象则存在window.event里面。

function handle_storage(e) {
    if (!e) { e = window.event; }
}

此时,变量 e 就是一个StorageEvent对象。这个对象有很多有用的属性。

StorageEvent 对象
属性类型说明
keystring增加、删除或者修改的那个键
oldValueany改写之前的旧值,如果是新增的元素,则是 null
newValueany改写之后的新值,如果是删除的元素,则是 null
url*string触发这个改变事件的页面 URL
*注意:url属性最早叫做uri。有些浏览器在标准修改之前就已经发布了。为了最大兼容,你应该检测是否存在这个url属性,如果没有,则要检测uri属性。

storage 事件不能取消。在handle_storage回调函数中,没有方法能够终止事件。浏览器只会简单地告诉你,“就是发生了!你现在什么也干不了了!我只是告诉你,它就是发生了。”

Leave a Reply