目录
25.1 cookie
HTTP cookie 通常也叫作 cookie,最初用于在客户端存储会话信息。这个规范要求服务器在响应 HTTP 请求时,通过发送 Set-Cookie HTTP 头部包含会话信息。例如,下面是包含这个头部的一个 HTTP 响应:
HTTP/1.1 200 OK
Content-type: text/html
Set-Cookie: name=value
Other-header: other-header-value
25.1.1 限制
cookie 是与特定域绑定的。设置 cookie 后,它会与请求一起发送到创建它的域。这个限制能保证 cookie 中存储的信息只对被认可的接收者开放,不被其他域访问。
通常,只要遵守以下大致的限制,就不会在任何浏览器中碰到问题:
不超过 300 个 cookie;
每个 cookie 不超过 4096 字节;
每个域不超过 20 个 cookie;
每个域不超过 81920 字节。
每个域能设置的 cookie 总数也是受限的,但不同浏览器的限制不同。
如果 cookie 总数超过了单个域的上限,浏览器就会删除之前设置的 cookie。IE 和 Opera 会按照最近 最少使用(LRU,Least Recently Used)原则删除之前的 cookie,以便为新设置的 cookie 腾出空间。Firefox 好像会随机删除之前的 cookie,因此为避免不确定的结果,最好不要超出限制。
25.1.2 cookie 的构成
cookie 在浏览器中是由以下参数构成的。
名称:唯一标识 cookie 的名称。cookie 名不区分大小写,因此 myCookie 和 MyCookie 是同一 个名称。不过,实践中最好将 cookie 名当成区分大小写来对待,因为一些服务器软件可能这样 对待它们。cookie 名必须经过 URL 编码。
值:存储在 cookie 里的字符串值。这个值必须经过 URL 编码。
域:cookie 有效的域。发送到这个域的所有请求都会包含对应的 cookie。这个值可能包含子域(如 www.wrox.com),也可以不包含(如.wrox.com 表示对 wrox.com 的所有子域都有效)。如果不明 确设置,则默认为设置 cookie 的域。
路径:请求 URL 中包含这个路径才会把 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。
安全标志:设置之后,只在使用 SSL 安全连接的情况下才会把 cookie 发送到服务器。例如,请 求 https://www.wrox.com 会发送 cookie,而请求 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
这个头部设置一个名为"name"的 cookie,这个 cookie 在 2007 年 1 月 22 日 7:10:24 过期,对 www.wrox.com 及其他 wrox.com 的子域(如 p2p.wrox.com)有效。 安全标志 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
这里创建的 cookie 对所有 wrox.com 的子域及该域中的所有页面有效(通过 path=/指定)。不过, 这个 cookie 只能在 SSL 连接上发送,因为设置了 secure 标志。
25.1.3 JavaScript 中的 cookie
25.1.4 子 cookie
为绕过浏览器对每个域 cookie 数的限制,有些开发者提出了子 cookie 的概念。子 cookie 是在单个 cookie 存储的小块数据,本质上是使用 cookie 的值在单个 cookie 中存储多个名/值对。最常用的子 cookie 模式如下:
name=name1=value1&name2=value2&name3=value3&name4=value4&name5=value5
cookie 的格式类似于查询字符串。这些值可以存储为单个 cookie,而不用单独存储为自己的名/ 值对。结果就是网站或 Web 应用程序能够在单域 cookie 数限制下存储更多的结构化数据。
25.1.5 使用 cookie 的注意事项
还有一种叫作 HTTP-only 的 cookie。HTTP-only 可以在浏览器设置,也可以在服务器设置,但只能 在服务器上读取,这是因为 JavaScript 无法取得这种 cookie 的值。 因为所有 cookie 都会作为请求头部由浏览器发送给服务器,所以在 cookie 中保存大量信息可能会影 响特定域浏览器请求的性能。保存的 cookie 越大,请求完成的时间就越长。即使浏览器对 cookie 大小有 限制,最好还是尽可能只通过 cookie 保存必要信息,以避免性能问题。
25.2 Web Storage
Web Storage 的第 2 版定义了两个对象:localStorage 和 sessionStorage。localStorage 是 永久存储机制,sessionStorage 是跨会话的存储机制。这两种浏览器存储 API 提供了在浏览器中不受页面刷新影响而存储数据的两种方式。
25.2.1 Storage 类型
Storage 类型用于保存名/值对数据,直至存储空间上限(由浏览器决定)。Storage 的实例与其他 对象一样,但增加了以下方法。
clear():删除所有值;不在 Firefox 中实现。
getItem(name):取得给定 name 的值。
key(index):取得给定数值位置的名称。
removeItem(name):删除给定 name 的名/值对
setItem(name, value):设置给定 name 的值。
25.2.2 sessionStorage 对象
sessionStorage 对象只存储会话数据,这意味着数据只会存储到浏览器关闭。这跟浏览器关闭时 会消失的会话 cookie 类似。存储在 sessionStorage 中的数据不受页面刷新影响,可以在浏览器崩溃 并重启后恢复。(取决于浏览器,Firefox 和 WebKit 支持,IE 不支持。)
因为 sessionStorage 对象是 Storage 的实例,所以可以通过使用 setItem()方法或直接给属 性赋值给它添加数据。下面是使用这两种方式的例子:
// 使用方法存储数据
sessionStorage.setItem("name", "Nicholas");
// 使用属性存储数据
sessionStorage.book = "Professional JavaScript";
可以结合 sessionStorage 的 length 属性和 key()方法遍历所有的值:
for (let i = 0, len = sessionStorage.length; i < len; i++){
let key = sessionStorage.key(i);
let value = sessionStorage.getItem(key);
alert(`${key}=`${value}`);
}
sessionStorage 对象应该主要用于存储只在会话期间有效的小块数据。如果需要跨会话持久存储 数据,可以使用 globalStorage 或 localStorage。
25.2.3 localStorage 对象
在修订的 HTML5 规范里,localStorage 对象取代了 globalStorage,作为在客户端持久存储 数据的机制。要访问同一个 localStorage 对象,页面必须来自同一个域(子域不可以)、在相同的端 口上使用相同的协议。 因 为 localStorage 是 Storage 的实例,所以可以像使用 sessionStorage 一样使用 localStorage。比如下面这几个例子:
// 使用方法存储数据
localStorage.setItem("name", "Nicholas");
// 使用属性存储数据
localStorage.book = "Professional JavaScript";
// 使用方法取得数据
let name = localStorage.getItem("name");
// 使用属性取得数据
let book = localStorage.book;
两种存储方法的区别在于,存储在 localStorage 中的数据会保留到通过 JavaScript 删除或者用户 清除浏览器缓存。localStorage 数据不受页面刷新影响,也不会因关闭窗口、标签页或重新启动浏览 器而丢失。
25.2.4 存储事件
每当 Storage 对象发生变化时,都会在文档上触发 storage 事件。使用属性或 setItem()设置 值、使用 delete 或 removeItem()删除值,以及每次调用 clear()时都会触发这个事件。这个事件的 事件对象有如下 4 个属性。
domain:存储变化对应的域。
key:被设置或删除的键。
newValue:键被设置的新值,若键被删除则为 null。
oldValue:键变化之前的值。
可以使用如下代码监听 storage 事件:
window.addEventListener("storage",
(event) => alert('Storage changed for ${event.domain}'));
对于 sessionStorage 和 localStorage 上的任何更改都会触发 storage 事件,但 storage 事 件不会区分这两者。
25.2.5 限制
与其他客户端数据存储方案一样,Web Storage 也有限制。具体的限制取决于特定的浏览器。一般 来说,客户端数据的大小限制是按照每个源(协议、域和端口)来设置的,因此每个源有固定大小的数据存储空间。分析存储数据的页面的源可以加强这一限制。
25.3 IndexedDB
IndexedDB 的设计几乎完全是异步的。为此,大多数操作以请求的形式执行,这些请求会异步执行, 产生成功的结果或错误。绝大多数 IndexedDB 操作要求添加 onerror 和 onsuccess 事件处理程序来确 定输出。
25.3.1 数据库
IndexedDB 是类似于 MySQL 或 Web SQL Database 的数据库。与传统数据库最大的区别在于, IndexedDB 使用对象存储而不是表格保存数据。IndexedDB 数据库就是在一个公共命名空间下的一组对 象存储,类似于 NoSQL 风格的实现。
使用 IndexedDB 数据库的第一步是调用 indexedDB.open()方法,并给它传入一个要打开的数据 库名称。如果给定名称的数据库已存在,则会发送一个打开它的请求;如果不存在,则会发送创建并打 开这个数据库的请求。这个方法会返回 IDBRequest 的实例,可以在这个实例上添加 onerror 和 onsuccess 事件处理程序。举例如下:
let db,
request,
version = 1;
request = indexedDB.open("admin", version);
request.onerror = (event) =>
alert(`Failed to open: ${event.target.errorCode}`);
request.onsuccess = (event) => {
db = event.target.result;
};
25.3.2 对象存储
建立了数据库连接之后,下一步就是使用对象存储。
假设要存储包含用户名、密码等内容的用户记录。可以用如下对象来表示一条记录:
let user = {
username: "007",
firstName: "James",
lastName: "Bond",
password: "foo"
};
25.3.3 事务
创建了对象存储之后,剩下的所有操作都是通过事务完成的。事务要通过调用数据库对象的 transaction()方法创建。任何时候,只要想要读取或修改数据,都要通过事务把所有修改操作组织 起来。最简单的情况下,可以像下面这样创建事务:
let transaction = db.transaction();
如果想要访问多个对象存储,可以给第 一个参数传入一个字符串数组:
let transaction = db.transaction("users", "readwrite");
25.3.4 插入对象
拿到了对象存储的引用后,就可以使用 add()或 put()写入数据了。这两个方法都接收一个参数, 即要存储的对象,并把对象保存到对象存储。这两个方法只在对象存储中已存在同名的键时有区别。这 种情况下,add()会导致错误,而 put()会简单地重写该对象。更简单地说,可以把 add()想象成插入 新值,而把 put()想象为更新值。因此第一次初始化对象存储时,可以这样做:
// users 是一个用户数据的数组
for (let user of users) {
store.add(user);
}
每次调用 add()或 put()都会创建对象存储的新更新请求。如果想验证请求成功与否,可以把请求 对象保存到一个变量,然后为它添加 onerror 和 onsuccess 事件处理程序:
// users 是一个用户数据的数组
let request,
requests = [];
for (let user of users) {
request = store.add(user);
request.onerror = () => {
// 处理错误
};
request.onsuccess = () => {
// 处理成功
};
requests.push(request);
}
25.3.5 通过游标查询
略
25.3.6 键范围
略
25.3.7 设置游标方向
略
25.3.8 索引
对某些数据集,可能需要为对象存储指定多个键。例如,如果同时记录了用户 ID 和用户名,那可能 需要通过任何一种方式来获取用户数据。为此,可以考虑将用户 ID 作为主键,然后在用户名上创建索引。
25.3.9 并发问题
IndexedDB 虽然是网页中的异步 API,但仍存在并发问题。如果两个不同的浏览器标签页同时打开了同一个网页,则有可能出现一个网页尝试升级数据库而另一个尚未就绪的情形。有问题的操作是设置 数据库为新版本,而版本变化只能在浏览器只有一个标签页使用数据库时才能完成。
25.3.10 限制
IndexedDB 的很多限制实际上与 Web Storage 一样。首先,IndexedDB 数据库是与页面源(协议、域 和端口)绑定的,因此信息不能跨域共享。这意味着 www.wrox.com 和 p2p.wrox.com 会对应不同的数据 存储。 其次,每个源都有可以存储的空间限制。当前 Firefox 的限制是每个源 50MB,而 Chrome 是 5MB。 移动版 Firefox 有 5MB 限制,如果用度超出配额则会请求用户许可。 Firefox 还有一个限制——本地文本不能访问 IndexedDB 数据库。Chrome 没有这个限制。因此在本 地运行本书示例时,要使用 Chrome。
25.4 小结
Web Storage 定义了两个对象用于存储数据:sessionStorage 和 localStorage。前者用于严格 保存浏览器一次会话期间的数据,因为数据会在浏览器关闭时被删除。后者用于会话之外持久保存数据。 IndexedDB 是类似于 SQL 数据库的结构化数据存储机制。不同的是,IndexedDB 存储的是对象,而 不是数据表。对象存储是通过定义键然后添加数据来创建的。游标用于查询对象存储中的特定数据,而 索引可以针对特定属性实现更快的查询。 有了这些存储手段,就可以在客户端通过使用 JavaScript 存储可观的数据。因为这些数据没有加密, 所以要注意不能使用它们存储敏感信息。