浏览器存储
参考 JavaScript 高级程序设计 第四版
# 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
2
3
4
5
6
这个 HTTP 响应会设置一个名为"name",值为"value"的 cookie。名和值在发送时都会经过 URL 编码。浏览器会存储这些会话信息,并在之后的每个请求中都会通过 HTTP 头部 cookie 再将它们发回服 务器,比如:
GET /index.jsl HTTP/1.1
Cookie: name=value
Other-header: other-header-value
2
3
4
5
6
这些发送回服务器的额外信息可用于唯一标识发送请求的客户端。
# 限制
cookie 是与特定域绑定的。设置 cookie 后,它会与请求一起发送到创建它的域。这个限制能保证 cookie 中存储的信息只对被认可的接收者开放,不被其他域访问。因为 cookie 存储在客户端机器上,所以为保证它不会被恶意利用,浏览器会施加限制。同时,cookie 也不会占用太多磁盘空间。
通常,只要遵守以下大致的限制,就不会在任何浏览器中碰到问题:
- 不超过 300 个 cookie;
- 每个 cookie 不超过 4096 字节;
- 每个域不超过 20 个 cookie;
- 每个域不超过 81 920 字节。
浏览器也会限制 cookie 的大小。大多数浏览器对 cookie 的限制是不超过 4096 字节,上下可以有一 个字节的误差。为跨浏览器兼容,最好保证 cookie 的大小不超过 4095 字节。这个大小限制适用于一个 域的所有 cookie,而不是单个 cookie。
如果创建的 cookie 超过最大限制,则该 cookie 会被静默删除。
# 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
2
3
4
5
6
这个头部设置一个名为"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 17 Content-type: text/html
Set-Cookie: name=value; domain=.wrox.com; path=/; secure
Other-header: other-header-value
2
3
4
5
要知道,域、路径、过期时间和 secure 标志用于告诉浏览器什么情况下应该在请求中包含 cookie。 这里创建的 cookie 对所有 wrox.com 的子域及该域中的所有页面有效(通过 path=/指定)。不过, 这个 cookie 只能在 SSL 连接上发送,因为设置了 secure 标志。这些参数并不会随请求发送给服务器,实际发送的只有 cookie 的名/值对。
# JavaScript 中的 cookie
BOM 的 document.cookie 返回包含页面中所有有效 cookie 的字符串(根据域、路径、过期时间和安全设置),以分号分隔,如下面的例子所示:
name1=value1;name2=value2;name3=value3
所有名和值都是 URL 编码的,因此必须使用 decodeURIComponent()解码。
在设置值时,可以通过 document.cookie 属性设置新的 cookie 字符串。这个字符串在被解析后会 添加到原有 cookie 中。设置 document.cookie 不会覆盖之前存在的任何 cookie,除非设置了已有的 cookie。设置 cookie 的格式如下,与 Set-Cookie 头部的格式一样:
name=value; expires=expiration_time; path=domain_path; domain=domain_name; secure
在所有这些参数中,只有 cookie 的名称和值是必需的。
例: document.cookie = "name=Nicholas";
# 使用 cookie 的注意事项
还有一种叫作 HTTP-only 的 cookie。HTTP-only 可以在浏览器设置,也可以在服务器设置,但只能 在服务器上读取,这是因为 JavaScript 无法取得这种 cookie 的值。
因为所有 cookie 都会作为请求头部由浏览器发送给服务器,所以在 cookie 中保存大量信息可能会影 响特定域浏览器请求的性能。保存的 cookie 越大,请求完成的时间就越长。即使浏览器对 cookie 大小有 限制,最好还是尽可能只通过 cookie 保存必要信息,以避免性能问题。
# sessionStorage
sessionStorage 对象只存储会话数据,这意味着数据只会存储到浏览器关闭。这跟浏览器关闭时 会消失的会话 cookie 类似。存储在 sessionStorage 中的数据不受页面刷新影响,可以在浏览器崩溃 并重启后恢复。
因为 sessionStorage 对象与服务器会话紧密相关,所以在运行本地文件时不能使用。存储在 sessionStorage 对象中的数据只能由最初存储数据的页面使用,在多页应用程序中的用处有限。
因为 sessionStorage 对象是 Storage 的实例,所以可以通过使用 setItem()方法或直接给属 性赋值给它添加数据。下面是使用这两种方式的例子:
// 使用方法存储数据
sessionStorage.setItem('name', 'Nicholas')
// 使用属性存储数据
sessionStorage.book = 'Professional JavaScript'
2
3
4
所有现代浏览器在实现存储写入时都使用了同步阻塞方式,因此数据会被立即提交到存储。具体 API 的实现可能不会立即把数据写入磁盘(而是使用某种不同的物理存储),但这个区别在 JavaScript 层 面是不可见的。通过 Web Storage 写入的任何数据都可以立即被读取。
对存在于 sessionStorage 上的数据,可以使用 getItem()或直接访问属性名来取得。下面是使 用这两种方式的例子:
注意 Storage 类型只能存储字符串, 非字符串数据在存储之前会自动转换为字符串。
注意,这种转换不能在获取数据时撤销。
// 使用方法取得数据
let name = sessionStorage.getItem('name')
// 使用属性取得数据
let book = sessionStorage.book
2
3
4
可以结合 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}`);
}
2
3
4
5
这里通过 key()先取得给定位置中的数据名称,然后使用该名称通过 getItem()取得值,可以依 次访问 sessionStorage 中的名/值对。
也可以使用 for-in 循环迭代 sessionStorage 的值:
for (let key in sessionStorage) {
let value = sessionStorage.getItem(key)
alert(`${key}=${value}`)
}
2
3
4
每次循环,key 都会被赋予 sessionStorage 中的一个名称;这里不会返回内置方法或 length 属性。
要从 sessionStorage 中删除数据,可以使用 delete 操作符直接删除对象属性,也可以使用 removeItem()方法。下面是使用这两种方式的例子:
// 使用 delete 删除值
delete sessionStorage.name
// 使用方法删除值
sessionStorage.removeItem('book')
2
3
4
sessionStorage 对象应该主要用于存储只在会话期间有效的小块数据。如果需要跨会话持久存储 数据,可以使用 localStorage。
# localStorage对象
localStorage 对象取代了 globalStorage,作为在客户端持久存储 数据的机制。要访问同一个 localStorage 对象,页面必须来自同一个域(子域不可以)、在相同的端 口上使用相同的协议。
因为 localStorage 是 Storage 的实例,所以可以像使用 sessionStorage 一样使用 localStorage。比如下面这几个例子:
// 使用方法存储数据
localStorage.setItem("name", "Nicholas");
// 使用属性存储数据
localStorage.book = "Professional JavaScript";
// 使用方法取得数据
let name = localStorage.getItem("name)
// 使用属性取得数据
let book = localStorage.book;
2
3
4
5
6
7
8
9
10
11
12
两种存储方法的区别在于,存储在 localStorage 中的数据会保留到通过 JavaScript 删除或者用户 清除浏览器缓存。localStorage 数据不受页面刷新影响,也不会因关闭窗口、标签页或重新启动浏览 器而丢失。
不同浏览器给 localStorage 和 sessionStorage 设置了不同的空间限制,但大多数会限制为每 个源 5MB。
# 总结
总结一下三大方案各自的特点以及适用场景:
Cookie
能被服务器指定,浏览器会自动在请求中带上
大小只有 4kb
大规模应用于广告商定位用户
配合 session 也是一个可行的登录鉴权方案
Web Storage
大小有 10MB,使用极其简单
但是只能存字符串,需要转义才能存 JS 对象
大部分情况下能完全替代 Cookie,且更安全
配合 token 可以实现更安全的登录鉴权
IndexedDB
储存空间无上限,功能极其强大
原生支持 JS 对象,能更好的储存数据
以数据库的形式储存数据,数据管理更规范
但是,原生 API 操作很繁琐,且有一定使用门槛