# 客户端存储

# 1、cookie

HTTP cookie 通常也叫作 cookie,最初用于在客户端存储会话信息。这个规范要求服务器在响应 HTTP 请求时,通过发送 Set-Cookie HTTP 头部包含会话信息。

# 1.1 限制

cookie 是与特定域绑定的。设置 cookie 后,它会与请求一起发送到创建它的域。这个限制能保证 cookie 中存储的信息只对被认可的接收者开放,不被其他域访问。

通常,只要遵守以下大致的限制,就不会在任何浏览器中碰到问题:

  • 不超过 300 个 cookie;
  • 每个 cookie 不超过 4096 字节;
  • 每个域不超过 20 个 cookie;
  • 每个域不超过 81 920 字节。

每个域能设置的 cookie 总数也是受限的,但不同浏览器的限制不同。例如:

  • 最新版 IE 和 Edge 限制每个域不超过 50 个 cookie;
  • 最新版 Firefox 限制每个域不超过 150 个 cookie;
  • 最新版 Opera 限制每个域不超过 180 个 cookie;
  • Safari 和 Chrome 对每个域的 cookie 数没有硬性限制。

如果 cookie 总数超过了单个域的上限,浏览器就会删除之前设置的 cookie。IE 和 Opera 会按照最近最少使用(LRU,Least Recently Used)原则删除之前的 cookie,以便为新设置的 cookie 腾出空间。Firefox 好像会随机删除之前的 cookie,因此为避免不确定的结果,最好不要超出限制。

cookie 在浏览器中是由以下参数构成的。

  • 名称:唯一标识 cookie 的名称。cookie 名不区分大小写。cookie 名必须经过 URL 编码。
  • 值:存储在 cookie 里的字符串值。这个值必须经过 URL 编码。
  • 域:cookie 有效的域。如果不明确设置,则默认为设置 cookie 的域。
  • 路径:请求 URL 中包含这个路径才会把 cookie 发送到服务器。
  • 过期时间:表示何时删除 cookie 的时间戳。默认情况下,浏览器会话结束后会删除所有 cookie。不过,也可以设置删除 cookie 的时间。用于指定删除 cookie 的具体时间。把过期时间设置为过去的时间会立即删除 cookie。
  • 安全标志:设置之后,只在使用 SSL 安全连接的情况下才会把 cookie 发送到服务器。

这些参数在 Set-Cookie 头部中使用分号加空格隔开。

Set-Cookie: name=value; expires=Mon, 22-Jan-07 07:10:24 GMT; domain=.wrox.com
1

document.cookie 返回包含页面中所有有效 cookie 的字符串(根据域、路径、过期时间和安全设置),以分号分隔

所有名和值都是 URL 编码的,因此必须使用 decodeURIComponent() 解码。

document.cookie = 'name=Nicholas';
1

# 1.4 HttpOnly

HttpOnly 可以在浏览器设置,也可以在服务器设置,但只能在服务器上读取,这是因为 JavaScript 无法取得这种 cookie 的值。

# 1.5 Secure

设置该属性后,只能通过 https 访问

# 1.6 SameSite

Chrome 51版本后 Cookie 新增属性,用来防止 CSRF 攻击和用户追踪。可以设置三个值 StrictLaxNone

  • Strict 完全禁止第三方 Cookie,跨站时,任何情况都不会发送 Cookie,只有当前网页的 URL 与请求目标一致,才会带上 Cookie
  • Lax 大多数情况不发送 Cookie,导航到目标网址的 Get 请求除外
  • None 设置该值必须同时设置 Secure

# 2、Web Storage

Web Storage 定义了两个对象:localStorage 和 sessionStorage。

localStorage 是永久存储机制,sessionStorage 是跨会话的存储机制。这两种浏览器存储 API 提供了在浏览器中不受页面刷新影响而存储数据的两种方式。

# 2.1 Storage 类型

Storage 类型用于保存名/值对数据,直至存储空间上限(由浏览器决定)。Storage 的实例与其他对象一样,但增加了以下方法。

  • clear():删除所有值;不在 Firefox 中实现。
  • getItem(name):取得给定 name 的值。
  • key(index):取得给定数值位置的名称。
  • removeItem(name):删除给定 name 的名/值对。
  • setItem(name, value):设置给定 name 的值。

Storage 类型只能存储字符串。非字符串数据在存储之前会自动转换为字符串。注意,这种转换不能在获取数据时撤销。

# 2.2 sessionStorage 对象

sessionStorage 对象只存储会话数据,这意味着数据只会存储到浏览器关闭。这跟浏览器关闭时会消失的会话 cookie 类似。存储在 sessionStorage 中的数据不受页面刷新影响,可以在浏览器崩溃并重启后恢复。(取决于浏览器,Firefox 和 WebKit 支持,IE 不支持。)

// 使用方法存储数据
sessionStorage.setItem('name', 'Nicholas');
// 使用属性存储数据
sessionStorage.book = 'Professional JavaScript';

// 使用方法取得数据
let name = sessionStorage.getItem('name');
// 使用属性取得数据
let book = sessionStorage.book;
1
2
3
4
5
6
7
8
9

所有现代浏览器在实现存储写入时都使用了同步阻塞方式,因此数据会被立即提交到存储。具体 API 的实现可能不会立即把数据写入磁盘(而是使用某种不同的物理存储),但这个区别在 JavaScript 层面是不可见的。通过 Web Storage 写入的任何数据都可以立即被读取。

可以结合 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}`));
}

for (let key in sessionStorage) {
  let value = sessionStorage.getItem(key);
  alert((`${key}` = `${value}`));
}
1
2
3
4
5
6
7
8
9
10

# 2.3 localStorage 对象

// 使用方法存储数据
localStorage.setItem('name', 'Nicholas');
// 使用属性存储数据
localStorage.book = 'Professional JavaScript';
// 使用方法取得数据
let name = localStorage.getItem('name');
// 使用属性取得数据
let book = localStorage.book;
1
2
3
4
5
6
7
8

# 2.4 存储事件

每当 Storage 对象发生变化时,都会在文档上触发 storage 事件。使用属性或 setItem() 设置值、使用 delete 或 removeItem() 删除值,以及每次调用 clear() 时都会触发这个事件。

这个事件的事件对象有如下 4 个属性。

  • domain:存储变化对应的域。
  • key:被设置或删除的键。
  • newValue:键被设置的新值,若键被删除则为 null。
  • oldValue:键变化之前的值。
window.addEventListener('storage', event =>
  alert('Storage changed for ${event.domain}'),
);
1
2
3

对于 sessionStorage 和 localStorage 上的任何更改都会触发 storage 事件,但 storage 事件不会区分这两者。

# 2.5 限制

不同浏览器给 localStorage 和 sessionStorage 设置了不同的空间限制,但大多数会限制为每个源 5MB。

# 3、IndexedDB

Indexed Database API 简称 IndexedDB,是浏览器中存储结构化数据的一个方案。IndexedDB 用于代替目前已废弃的 Web SQL Database API。IndexedDB 背后的思想是创造一套 API,方便 JavaScript 对象的存储和获取,同时也支持查询和搜索。

IndexedDB 的设计几乎完全是异步的。为此,大多数操作以请求的形式执行,这些请求会异步执行,产生成功的结果或错误。绝大多数 IndexedDB 操作要求添加 onerror 和 onsuccess 事件处理程序来确定输出。

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;
};
1
2
3
4
5
6
7
上次更新: 11/28/2023, 7:06:34 PM