浏览器之客户端存储

2022-08-25 15:06:02 浏览数 (1)

前言

大家好,我是柒八九。我们在网络拾遗之Http缓存文章中,从网络协议的视角介绍了网站「客户端缓存」 中的HTTP缓存策略,并对「强缓存」「协商缓存」做了较为详细的介绍。

而今天,这篇文章,打算介绍客户端缓存的另外一种类别 -- 本地缓存(也可以叫客户端存储)

还是老样子。赶紧上车。发车走起。

面试加油站

  1. 存储在「客户端」上的cookie 1. 「每个 cookie」 不超过 「4 KB」 2. 「每个域」不超过 20 个 cookie
  2. Web Storage 的目的是解决通过「客户端存储不需要频繁发送回服务器的数据」时使用 cookie 的问题
  3. Web Storage 定义了「两个对象」localStoragesessionStorage。 1. localStorage「永久存储」机制 2. sessionStorage「跨会话的存储」机制 3. 「单个域」的大小为「5M」
  4. IndexedDB 是一个事务型数据库系统

文章概要

  1. cookie
  2. Web Storage
    1. sessionStorage
    2. localStorage
  3. IndexDB

1. cookie

HTTP cookie 通常也叫作 cookie,最初用于在「客户端存储会话信息」

这个规范要求「服务器」「响应 HTTP 请求」时,通过发送 Set-Cookie HTTP 「头部包」含会话信息。

例如,下面是包含这个头部的一个 HTTP「响应」

代码语言:javascript复制
HTTP/1.1 200 OK
Content-type: text/html
  Set-Cookie: name=value
Other-header: other-header-value

这个 HTTP 响应会设置一个「名」为"name",「值」为"value"的 cookie。名和值在发送时都会经过 「URL 编码」

浏览器会「存储」这些会话信息,并在之后的「每个请求」中都会通过 HTTP 头部 cookie 再「将它们发回服务器」

代码语言:javascript复制
GET /index.js HTTP/1.1
  Cookie: name=value
Other-header: other-header-value 

限制

cookie 是与「特定域」绑定的 ❞

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

cookie 存储在「客户端」机器上,所以有很多针对安全性的限制

  • 不超过 300 个 cookie
  • 「每个 cookie」 不超过 「4 KB」
  • 「每个域」不超过 20 个 cookie
  • 「每个域」不超过 「80KB」

「不同浏览器的针对每个域能设置多少cookie有不同的限制」

如果 cookie 总数超过了「单个域的上限」,浏览器就会删除之前设置的 cookie

如果创建的 cookie 超过「最大限制」,则该 cookie 会被「静默删除」

cookie 的构成

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

  • 「名称」 1. 「唯一标识」 cookie 的名称 2.「不区分大小写」 3. 必须经过 「URL 编码」
  • 「值」: 1. 存储在 cookie 里的「字符串值」 2. 必须经过 「URL 编码」
  • 「域」 1. cookie 有效的域 2. 发送到这个域的「所有请求」都会包含对应的 cookie
  • 「过期时间」 1. 表示「何时删除」 cookie「时间戳」 2. 默认情况下,浏览器「会话结束」后会「删除所有 cookie」 3.这个值是 GMT 格式(Wdy, DD-Mon-YYYY HH:MM:SS GMT),用于指定删除 cookie 的「具体时间」 4.把过期时间设置为「过去的时间」会立即删除 cookie
  • 「路径」 1. 「请求 URL」「包含这个路径」才会把 cookie 发送到服务器
  • 「安全标志」 1. 设置之后,只在使用 SSL 安全连接的情况下才会把 cookie 发送到服务器

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

代码语言:javascript复制
HTTP/1.1 200 OK
Content-type: text/html
  Set-Cookie: name=value; expires=expiration_time; 
  path=domain_path; domain=domain_name; secure
Other-header: other-header-value

「安全标志」 secure 是 cookie 中「唯一的非名/值对」,只需一个 secure 就可以了 ❞

JS 中的 cookie

❝在 JS 中只有 BOMdocument.cookie 属性用于处理 cookie

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

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

「设置值时」,可以通过 document.cookie 属性设置「新的」 cookie 字符串。这个字符串在被解析后会「添加到原有 cookie 中」

❝设置 document.cookie 「不会覆盖之前存在的任何 cookie,除非设置了已有的cookie」

「设置」 cookie 的格式如下,与 Set-Cookie 头部的格式一样:

代码语言:javascript复制
name=value; expires=expiration_time; 
path=domain_path; domain=domain_name; secure

❝在所有这些参数中,「只有 cookie 的名称和值是必需」的 ❞

下面是个简单的例子:

代码语言:javascript复制
document.cookie =  encodeURIComponent("name")   "="  
                   encodeURIComponent("Nicholas");

创建一个名为name,值为bcnz789会话 cookie,这个 cookie 在「每次客户端向服务器发送请求时」都会被带上,在「浏览器关闭时就会被删除」

子 cookie

为绕过浏览器对「每个域 cookie 数的限制」,有些开发者提出了「子 cookie」 的概念。

❝子 cookie 是在「单个 cookie 存储的小块数据」,本质上是使用 「cookie 的值」「单个」 cookie 中存储「多个名/值对」

代码语言:javascript复制
name=name1=value1&name2=value2&name3=value3&name4=value4&name5=value5

注意事项

有一种叫作 HTTP-onlycookieHTTP-only 可以在浏览器设置,也可以在服务器设置,但「只能在服务器上读取」,这是因为 「JS 无法取得这种 cookie 的值」

因为「所有 cookie 都会作为请求头部由浏览器发送给服务器」,所以在 cookie 中保存大量信息可能会「影响特定域浏览器请求的性能」。保存的 cookie 越大,请求完成的「时间就越长」。即使浏览器对 cookie 大小有限制,最好还是尽可能「只通过 cookie 保存必要信息」,以避免性能问题。

Web Storage

Web Storage 的目的是解决通过「客户端存储不需要频繁发送回服务器的数据」时使用 cookie 的问题 ❞

Web Storage 规范「最新的版本」是第 2 版,这一版规范主要有「两个目标」

  1. 提供「在 cookie 之外」「存储会话数据」的途径
  2. 提供「跨会话持久化存储大量数据」的机制

Web Storage 的第 2 版定义了「两个对象」localStoragesessionStorage

  • localStorage「永久存储」机制
  • sessionStorage「跨会话的存储」机制

这两种浏览器存储 API 提供了在「浏览器中不受页面刷新影响而存储数据」的两种方式。

Storage 类型

Storage 类型用于保存「名/值对」数据,直至存储空间上限(由浏览器决定)。

Storage 的实例增加了以下方法

  • clear():删除「所有值」
  • getItem(name):取得「给定」 name 的值。
  • key(index):取得「给定数值位置」的名称。
  • removeItem(name):删除「给定」 name 的名/值对。
  • setItem(name, value):设置给定 name 的值。

通过 length 属性可以确定 Storage 对象中保存了多少名/值对。

sessionStorage 对象

sessionStorage 对象「只存储会话数据」,这意味着数据「只会存储到浏览器关闭」

这跟浏览器关闭时会消失的「会话 cookie」 类似。

存储在 sessionStorage 中的数据「不受页面刷新影响」,可以在浏览器崩溃并重启后恢复。sessionStorage 对象与「服务器会话」紧密相关,所以在「运行本地文件时不能使用」

因为 sessionStorage 对象是 Storage 的实例,所以可以通过使用 setItem()方法或直接「给属性赋值」给它添加数据。

代码语言:javascript复制
// 使用方法存储数据
sessionStorage.setItem("name", "wl");

// 使用属性存储数据
sessionStorage.name = "wl"; 

「所有」现代浏览器在「实现存储写入时」都使用了「同步阻塞方式」,因此数据会被「立即提交到存储」。也就是说

❝通过 Web Storage 写入的任何数据都可以「立即被读取」

对存在于 sessionStorage 上的数据,可以使用 getItem()或直接访问属性名来取得。

代码语言:javascript复制
// 使用方法取得数据
let name = sessionStorage.getItem("name");

// 使用属性取得数据
let name2 = sessionStorage.name;

也可以结合 sessionStoragelength 属性和 key()方法遍历「所有的值」

代码语言:javascript复制
  let len = sessionStorage.length;
for (let i = 0; i < len; i  ){
   let key = sessionStorage.key(i);
   let value = sessionStorage.getItem(key);
   console.log(`${key}=`${value}`);
}

也可以使用 for-in 循环迭代 sessionStorage 的值:

代码语言:javascript复制
for (let key in sessionStorage){
   let value = sessionStorage.getItem(key);
   console.log(`${key}=${value}`);
}

localStorage 对象

localStorage 作为在「客户端持久存储数据」的机制 ❞

要访问「同一个」 localStorage 对象,页面「必须」来自

  • 同一个域(子域不可以)
  • 在相同的端口
  • 使用相同的协议

「同源页面」,可以访问同一个localStorage

localStorageStorage 的实例,所以可以像使用 sessionStorage 一样使用localStorage

代码语言:javascript复制
// 使用方法存储数据
localStorage.setItem("name", "wl");
// 使用属性存储数据
localStorage.book = "bc";

// 使用方法取得数据
let name = localStorage.getItem("name");
// 使用属性取得数据
let book = localStorage.book;

两种存储方法的「区别在于」存储在 localStorage 中的数据会保留到「通过 JS 删除」或者「用户清除浏览器缓存」

localStorage 数据「不受页面刷新影响」,也不会因关闭窗口、标签页或重新启动浏览器而丢失

存储事件

❝每当 Storage 对象发生变化时,都会在「文档上」触发 storage 事件 ❞

使用属性或 setItem()设置值、使用 deleteremoveItem()删除值,以及每次调用 clear()时都会触发这个事件。

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

  • domain:存储变化对应的域
  • key:被设置或删除的键
  • newValue:键被设置的「新值」,若键被删除则为 null。
  • oldValue:键变化之前的值

代码监听 storage 事件:

代码语言:javascript复制
window.addEventListener("storage",
   (event) => alert('发生变化的域:${event.domain}'));

限制

一般来说,客户端数据的「大小限制」是按照「每个源」(协议、域和端口)来设置的,因此每个源有「固定大小的数据存储空间」

❝大部分浏览器将localStoragesessionStorage 限制为「每个源 5MB」

IndexedDB

Indexed Database API 简称 IndexedDB,是浏览器中存储「结构化数据」的一个方案 ❞

IndexedDB 的设计几乎完全是「异步的」。为此,大多数操作以「请求的形式」执行,这些请求会「异步执行」,产生成功的结果或错误。

数据库

IndexedDB 是类似于 MySQLWeb SQL Database「数据库」

与传统数据库最大的「区别」在于,IndexedDB 使用「对象存储」而不是表格保存数据。IndexedDB 数据库就是在一个公共命名空间下的「一组对象存储」

使用 IndexedDB 数据库的「第一步」是调用 indexedDB.open()方法,并给它传入一个要打开的数据库名称。

  • 如果给定名称的数据库「已存在」,则会发送一个「打开」它的请求
  • 如果「不存在」,则会发送「创建并打开」这个数据库的请求

这个方法会返回 IDBRequest 的实例,可以在这个实例上添加 onerroronsuccess 事件处理程序。

代码语言:javascript复制
let db,
 request,
 version = 1;
 
request = indexedDB.open("admin", version);

request.onerror = (event) =>
   alert(`错误码: ${event.target.errorCode}`);
   
request.onsuccess = (event) => {
   db = event.target.result;
}; 

对象存储

建立了数据库连接之后,下一步就是「使用对象存储」。创建对象存储时「必须指定一个键」

upgradeneeded 事件中设置对象存储信息。

代码语言:javascript复制
request.onupgradeneeded = (event) => {
 const db = event.target.result;
 // 如果存在则删除当前 objectStore。
 if (db.objectStoreNames.contains("users")) {
     db.deleteObjectStore("users");
 }
 // 设置对象存储,并指定usename为主键
 db.createObjectStore("users", { keyPath: "username" });
}; 

事务

创建了对象存储之后,剩下的所有操作都是通过「事务」完成的。

事务要通过调用数据库对象的 transaction()方法创建。「任何时候」,只要想要读取或修改数据,都要「通过事务把所有修改操作组织起来」

代码语言:javascript复制
let transaction = db.transaction("users");

保在事务期间只加载 users 对象存储的信息。(参数也可以是数组)。

有了「事务的引用」,就可以使用 objectStore()方法并传入「对象存储的名称」以访问特定的对象存储。

  • add()/put():添加和更新对象
  • get():获取对象
  • delete():删除对象
  • clear():删除所有对象

这 5 个方法都创建「新的请求对象」

代码语言:javascript复制
const transaction = db.transaction("users"),
      store = transaction.objectStore("users"),
      request = store.get("007");
 
request.onerror = (event) => alert("没有该项数据");
request.onsuccess = (event) => alert(event.target.result.firstName);

「一个事务可以完成任意多个请求」,所以事务对象本身也有「事件处理程序」

  • onerror
  • oncomplete

这两个事件可以用来获取「事务级的状态信息」

代码语言:javascript复制
transaction.onerror = (event) => {
 // 整个事务被取消
};

transaction.oncomplete = (event) => {
 // 整个事务成功完成
}; 

限制

  • IndexedDB 数据库是与页面源(协议、域和端口)绑定的,因此「信息不能跨域共享」 意味着 www.wl.combc.wl.com 会对应不同的数据存储
  • 「每个源」都有可以存储的空间限制 针对IndexDB的简化使用
    • 在 Chrome 「正常模式」下:
    • 在 Chrome 「隐身模式」下:固定 100MB 的大小

在官网提供了很多基于IndexDB包装的库,隐藏了一些比较「啰嗦」的数据库实例化等操作。

然后「谁用了,都说好」

其他不常见的数据持久化方案

WebSQL

用于存储较大量数据的缓存机制。

  • 将数据以数据库二维表的形式存储在客户端
  • 允许SQL语句的查询
  • 让浏览器实现小型数据库存储功能
  • 不是H5规范

核心方法

  • openDatabase()
  • transaction()
  • executeSql()

「已废弃并且被IndexDB所替代」

Application Cache

允许浏览器通过manifest配置文件在本地「有选择」的存储JS/CSS/图片等静态资源的文件级缓存机制

当页面「不是首次打开」,通过特定的manifest文件配置描述来选择本地Application Cache里的文件。

「已废弃并且被ServerWorkers所替代」

在昨天的文章中,我们介绍了Service Worker,而服务工作线程的一个主要能力是可以「通过编程方式实现真正的网络请求缓存机制」,其中利用CacheStorage实现该功能。

后记

参考资料:JS高级程序设计第四版

0 人点赞