web应用允许使用浏览器提供的API实现将数据存储在用户电脑上。这种客户端存储相当于赋予了web浏览器记忆功能。比方说,web应用就可以用这些方式来“记住”用户的偏好甚至是用户的所有状态信息,以便准确地“回忆”起用户上一次访问的位置。客户端存储遵循“同源策略”,因此不同站点的页面是无法读取对于存储的数据。而同一站点的不同的页面之间是可以互相共享存储数据的,它为我们提供了一种通信机制,例如一个表单的数据可以显示在另一个页面中。web应用可以选择他们存储数据的有效期:比如采用临时存储可以让数据保存至当前窗口关闭或浏览器退出;采用永久存储,可以将数据永久地存储在硬盘上,数年或者数月不失效。
客户端存储有以下几种形式:
web存储
web存储最初作为HTML5的一部分被定义为API形式,但是后来被剥离出来作为独立的一份标准了。目前包含IE8在内的主流浏览器(可交互地)都实现了。web储存标准所描述的API包含localStorage和sessionStorage对象,这两个对象实际上是持久化的关联数组,是名值对的映射表,“名”和“值”都是字符串。web存储易于使用,支持大容量(但非无限量)数据存储同时兼容当前所有主流浏览器(但不兼容早期浏览器),本章第一1节会对localStorage和sessionStorage这两个对象做详细介绍。
cookie
cookie是一种早期的客户端存储机制,期初是为征对服务器端脚本设计使用的。尽管在客户端提供了非常繁琐的javascript API来操作cookie,但它们难用至极,而且只适合少量的文本存储。不仅如此,任何以cookie形式存储的数据,不论服务器端是否需要,每一次HTTP请求都要把这些数据传输到服务器端。cookie目前仍然被大量的客户端程序员使用的一个重要的原因是:所有新旧浏览器都支持它。但是,随着Web Storage的普及,cookie将最终回归到最初的形态:作为一种被服务端脚本使用的客户存储机制。本章第2节将详细介绍cookie。
IE User Data
微软在IE5之后的IE浏览器实现了专属它的客户端存储机制-“User Data”。userData可以实现一定量的字符串数据存储,对于IE8前的IE浏览器,可以将其用做是Web存储的替代方案。关于userData的API会在本章第3节进行相关的介绍。
离线web应用
HTML5标准定义了一组“离线web应用”API,用于缓存web页面以及相关资源(图片,脚本,CSS文件等)。它的现实是将web应用整体存储在客户端,而不仅是存储数据。它能够让web应用“安装”在客户端。这样一来,哪怕网络不可用的时候web应用依然是可用的。离线web应用的相关内容会在本章第4节介绍。
web数据库
为了能够让开发者像使用数据那样操作大量的数据,很多主流浏览器纷纷实现在其中开始集成客户端数据库功能。Safari,Chrome,和Opera都内置了SQL数据库的客户端API。遗憾的是这类的API标准化最终以失败而告终,并且,Firefox和IE看来不打算实现这类API。目前还有一种正在标准化的API,称为“索引数据库API”。调用该API返回的是一个不包含查询术语的简单数据库对象。这两种客户端数据库API都是异步的,都使用了事件处理机制(类似DOM事件的机制),这样的方式多多少少都显得复杂,本章不会对它们做介绍。在20章第8节会简要介绍所有索引数据库API同时提供一些例子。
文件系统API
在第8章介绍过现代的浏览器都支持一个文件对象,用以将选择的文件通过xmlHttPRequest上传到服务器。与之相关的规范(草案阶段)定义了一组API,用于操作一个私有的本地文件系统。该系统中,可以进行对文件的读写操作。目前正在标准化当中。这些API在20章第7节中做介绍。这些API被广泛地实现和支持,web应用可以使用类似文件的存储机制,这对大部分程序员来说再熟悉不过了。
存储、安全和隐私
web浏览器通常会提供“记住密码”功能,这些密码会以加密的形式安全地存储在硬盘上。然而本章介绍的任何形式客户端数据存储都不牵扯加密:任何存储在用户硬盘上的数据都是未加密形式。
还要记住一点:很多web用户不信任那行使用cookie和其它客户端存储机制来做类似“跟踪”功能的网站。所以,尽量尝试本章讨论的存储机制来网站升级用户体验;而不是用它们来收集和侵犯隐私相关的数据。如果网站滥用客户端存储,用户将禁用该功能。这样一来不仅达不到效果,还会导致依赖客户端存储的网站完全不可用。
1.localStorage和sessionStorage
实现了“web存储”草案标准的浏览器在window对象上定义了两个属性,localStorage和sessionStorage。这练个属性代表同一个Storage对象——一个持久化关联数组,数组使用字符串来索引,存储的值也是字符串形式的,Storage对象在使用上和一般的Javascript没有什么两样,设置对象的属性为字符串值,随后浏览器会将值存储起来。lcoalStorage和sessionStorage两者的区别在于存储的有效期和作用域不同:数据可以存储多长时间及拥有数据的访问权。
下面我们对存储的有效期和作用域进行详细的解释。不过,再此之前,我们看些例子,下面的代码使用的是localStorage,但是它对sessionStorage同样适用:
var name = localStorage.username; //查询一个存储的值 name = localStorage["username"]; //等于数组表示法 if (!name) { name = prompt("请输入你的名字"); //询问用户一个答案 localStorage.username = name; //存储用户的答案 } //迭代所有存储的那么/value对 for (var name in localStorage) { //迭代所有存储的名字 var value = localStorage[name]; //查询每个名字对应的值 }
Storage对象还定义了一些诸如存储、获取、遍历和删除的方法。这些方法会在第ii小节中介绍。
“web存储”草案标准指出,我们既可以存储结构话的数据(对象和数组),也可以存储原始类型数据,还可以存储诸如日期、正则表达式甚至是文件对象在内的内置类型和数据。(在一些浏览器中,仅仅支持存储字符串类型数据,如果要存储和其它类型的书,不得不自己手动进行和编码解码),如下例子所示:
//当存储一个数字的时候,会自动转换成一个字符串 //但是,当获取该值的时候,别忘了手动转换成数字类型 localStorage.x = 10; var x = parseInt(localStorage.x); // //同样,存储一个日期类型数据的时候进行编码,获取的时候进行解码 localStorage.lastRead = (new Date()).toUTCString(); var lastdate = new Date(Date.parse(localStorage.lastRead)); //使用JSON可以使得对基本数据类型的编码规则变得很方法 var data = new Date(); localStorage.data = JSON.stringify(data)//编码后存储 var data = JSON.parse(localStorage.data)//获取数值后再解码
i.存储有效期和作用域
localStorage和sessionStorage的区别在于存储的有效期和作用域不同。通过localStorage存储的数据是永久性的,除非web应用立刻删除存储的数据。或者用户通过设置浏览器配置(浏览器提供的特定UI)来删除,否则数据将一直保留在用户电脑上,永不过期。
localStorage的作用域是限定在文档源(document origin)级别的。正如11章6节ii小节描述的,文档源是通过协议、主机名及端口三者来确定的。因此,每个url都有不同的文档源。
http://www.ahthw.com //协议:http;主机名:www.ahthw.com https://www.ahthw.com //不同协议 http://study.ahthw.com //不同主机名 http://www.ahthw.com:8080 //不同端口
同源文档间共享localStorage数据(不论该源的脚本是否真正的访问的localStorage)。它们可以互相读取对方的数据,甚至可以覆盖对方的数据。但是,非同源的文档见互相都不能读取和共享或覆盖对方的数据(即使它们的脚本来自同一台第三方服务器也不行)。
需要注意的是localStorage的作用域也收到浏览器供应商的限制。如果你使用的Firefox访问站点那么下一次用另外一个浏览器打开的时候,那么本次是无法获取上次的数据的。
通过sessionStorage存储的数据和通过localStorage存储的数据有效期也是不同的:sessionStorage的有效期存储数据的脚本所在最顶层的窗口或者是浏览器标签页是一样的。一旦窗口或者标签页被永久关闭了,那么所有通过sessionStorage存储的数据也被删除了(需要注意的是,现代的浏览器具备了一些关闭标签页随后恢复上一次浏览器回话的功能,可能预知相关的sessionStorage会更长些)。
和localStorage一样,sessionStorage的作用域也是限定在文档源中,因此,非同源的文档之间是无法贡献sessionStorage的。
需要注意的是:这里提到的基于窗口作用域的sessionStorage指的窗口是顶级窗口。如果一个浏览器标签包含两个<iframe>元素,它们是同源的,那么两者之间是可以共享的sessionStorage的。
ii.存储API
localStorage和sessionStorage通常是被当做普通的javascript对象使用。通过设置属性来存储字符串值,查询该属性来读取该值。这两个对象还提供了两个正式的API,调用setItem(),将对于的名字和值传递进去,可以实现数据存储。调用getItem()方法,将名字传递进去,能获取对于的值。调用removeItem()方法,将名字传递进去,可以删除对于的数据。(在非IE8的浏览器中,还可以使用delete操作符来删除数据,就和普通对象使用的delete操作符一样)调用clear()方法(不需要参数),可以删除所有存储的数据。最后,使用length和key()方法,传入0~length~1的数字,可以枚举所有存储数据的名字。下面是使用localStorage的例子,下面这些代码对sessionStorage也适用。
localStorage.setItem("x", 1); //以"x"的名字存储一个数值 localStorage.getItem("x"); //获取x的数值 //枚举所有的存储的名字/值对 for(var i = 0;i<localStorage.length;i++){//length 表示了所有名字/值对 var name = localStorage.key(i); //获取第i对的名字 var value = localStorage.getItem(name);//获取该对的值 } localStorage.removeItem("x")//删除"x"项 localStorage.clear();//全部删除
尽管通过设置和查询属性能更加方便地存储和获取数据,但是有些时候还是不得不适应上面所提到的这些方法。比方说其中clear()方法是唯一能删除存储对象中的名/值对的方式。同样的还有,removeItem()是也是唯一通过山粗单个名/值对的方式。(因为IE8不支持delete操作符)。
如果浏览器提供商完全实现了“web存储”的标准,只对对象和数组类型的数据存储,那么就会又多了一个使用类似用setItem()和getItem()这类方法的理由。对象和数组的值通常是可变的,因此存储要也要求存储它们的副本,以确保之后对任何这类对象的改变不影响到存储的对象。同样的 ,在获取该对象的时候,也要求获取是该对象的副本,以确保已获取的对象的改动不会影响到存储的对象。而这类操作如果如果使用基于属性的API就会令人困惑。考虑如下这段代码(假设浏览器已经支持了结构化数据存储)。
localStorage.o = {x:1};//存储一个带有"x"属性的对象 localStorage.o.x = 2; //试图设置该对象的属性值 localStorage.o.x; //=>undefined x没有改变
上述的第二行代码要设置存储对象的属性值,但事实上,它获取到的只是存储的对象的副本,随后设置了该对象的属性值,然后就将该副本废弃了。真正的存储对象保持不变。对于这种情况,使用getItem()就不会让人这么困惑了。
localStorage.getItem("o").x = 2;//我们并不像存储2
另外,还有另外一个显式的机遇存储API的理由就是:在还不支持“web存储”标准的浏览器中,其它的存储机制的顶层API对其也是兼容的。下面这段代码使用cookie和IE userData来实现存储API。如果使用基层方法的API,当localStorage可用的时候就可以使用它编写代码,而当它在其它浏览器上不可用的时候依然可以依赖于其它的存储机制(完全兼容)。如下代码所示:
//识别出是哪类存储机制 var memory = window.localStorage || (window.userDataStorage && new userDataStorage()) || new cookieStorage(); //然后在对于的机制中查询数据 var username = memory.getItem("username");
iii.存储事件
无论什么时候,存储在localStorage或者sessionStorage的数据发生改变,浏览器都会在其它对该数据可见的窗口对象上出发存储事件(但是在对数据改变的窗口上是不出发的)。如果浏览器有两个标签页都打开了来自于同源的页面,其中一个在localStorage上存储了数据,那么在另外一个标签页就会接受到一个存储事件。要记住,sessionStorage的作用域是限制在顶层窗口的,因此对于sessionStorage的改变只有相关牵连的窗口才会触发存储事件。像给已经存在的存储项设置一个一模一样的值,或者是删除一个本来就不存在的项存储都是不会触发存储事件的。
为存储事件注册处理程序可以通过addEventListener()方法,(或者在IE下使用attachEvent方法)。在绝大多浏览器中,还可以给Window对象设置onstorage属性的方法,不过Firefox不支持该属性。
与事件相关的事件对象有5个非常重要的属性(遗憾的是IE8不支持他们)
key
被设置或移除项的键名。如果调用clear()函数,那么该属性值就为null.
newValue
保存该项新值;或者调用removeItem()时,该属性值为null。
oldvalue
改变或删除该项前,保存该项的原先值;当插入一个新项的时候,该属性值为null
storageArea
这个属性值就好比目标window对象上的locaStorage属性或者是sessionStorgae属性
url
触发该存储变化脚本所在的文档URL
最后,要注意的是:localStorage和sessionStorage存储机制都是广播机制的,浏览器会对目前正在访问的同源站点所有窗口发送消息。举个例子:如果一个用户要求网站停止动画效果时,那么该站点可能会在localStorage中存储该用户的首选项,这样依赖,以后再访问该站点的时候自动停止该动画效果了。因为存储了首选项,导致触发的一个存储事件让其它的同源页面也获得了一个用户请求。再比如,一个机遇web的图片编辑应用,通常允许在其它窗口中展现工具条。当用户选择一个工具的时候,应用就可以使用localStorage来存储用户的当前状
新闻热点
疑难解答