古希腊人在阿波罗神庙柱子上刻了一句箴言:认识你自
前言
大家好,我是柒八九
。我们在网络拾遗之Http缓存文章中,从网络协议的视角介绍了网站客户端缓存 中的HTTP
缓存策略,并对强缓存和协商缓存做了较为详细的介绍。
而今天,这篇文章,打算介绍客户端缓存的另外一种类别 -- 本地缓存(也可以叫客户端存储)
还是老样子。赶紧上车。发车走起。
::: block-2
面试加油站
- 存储在客户端上的
cookie
1. 每个 cookie 不超过 4 KB
2. 每个域不超过20
个 cookieWeb Storage
的目的是解决通过客户端存储不需要频繁发送回服务器的数据时使用 cookie 的问题Web Storage
定义了两个对象:localStorage
和sessionStorage
。
1.localStorage
是永久存储机制
2.sessionStorage
是跨会话的存储机制
3. 单个域的大小为5MIndexedDB
是一个事务型数据库系统 :::
文章概要
- cookie
- Web Storage
- sessionStorage
- localStorage
- IndexDB
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 复制代码
这个 HTTP 响应会设置一个名为"name",值为"value"的 cookie。名和值在发送时都会经过 URL 编码。
浏览器会存储这些会话信息,并在之后的每个请求中都会通过 HTTP 头部 cookie 再将它们发回服务器。
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
头部中使用分号加空格隔开
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 中只有
BOM
的document.cookie
属性用于处理cookie
document.cookie
返回包含页面中所有有效cookie
的字符串(根据域、路径、过期时间和安全设置),以分号分隔。
所有名和值都是 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 = encodeURIComponent("name") + "=" + encodeURIComponent("Nicholas"); 复制代码
创建一个名为name
,值为bcnz789
会话 cookie,这个 cookie 在每次客户端向服务器发送请求时都会被带上,在浏览器关闭时就会被删除。
子 cookie
为绕过浏览器对每个域 cookie 数的限制,有些开发者提出了子 cookie的概念。
子 cookie 是在单个 cookie 存储的小块数据,本质上是使用 cookie 的值在单个 cookie 中存储多个名/值对
name=name1=value1&name2=value2&name3=value3&name4=value4&name5=value5 复制代码
注意事项
有一种叫作 HTTP-only
的 cookie
。HTTP-only
可以在浏览器设置,也可以在服务器设置,但只能在服务器上读取,这是因为 JS 无法取得这种 cookie 的值。
因为所有 cookie 都会作为请求头部由浏览器发送给服务器,所以在 cookie
中保存大量信息可能会影响特定域浏览器请求的性能。保存的 cookie
越大,请求完成的时间就越长。即使浏览器对 cookie 大小有限制,最好还是尽可能只通过 cookie 保存必要信息,以避免性能问题。
Web Storage
Web Storage
的目的是解决通过客户端存储不需要频繁发送回服务器的数据时使用 cookie 的问题
Web Storage
规范最新的版本是第 2 版,这一版规范主要有两个目标
- 提供在
cookie
之外的存储会话数据的途径 - 提供跨会话持久化存储大量数据的机制
Web Storage
的第 2 版定义了两个对象:localStorage
和 sessionStorage
。
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()
方法或直接给属性赋值给它添加数据。
// 使用方法存储数据 sessionStorage.setItem("name", "wl"); // 使用属性存储数据 sessionStorage.name = "wl"; 复制代码
所有现代浏览器在实现存储写入时都使用了同步阻塞方式,因此数据会被立即提交到存储。也就是说
通过 Web Storage 写入的任何数据都可以立即被读取
对存在于 sessionStorage
上的数据,可以使用 getItem()
或直接访问属性名来取得。
// 使用方法取得数据 let name = sessionStorage.getItem("name"); // 使用属性取得数据 let name2 = sessionStorage.name; 复制代码
也可以结合 sessionStorage
的 length
属性和 key()
方法遍历所有的值
+ 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
的值:
for (let key in sessionStorage){ let value = sessionStorage.getItem(key); console.log(`${key}=${value}`); } 复制代码
localStorage 对象
localStorage
作为在客户端持久存储数据的机制
要访问同一个localStorage
对象,页面必须来自
- 同一个域(子域不可以)
- 在相同的端口
- 使用相同的协议
同源页面,可以访问同一个
localStorage
localStorage
是 Storage
的实例,所以可以像使用 sessionStorage
一样使用 localStorage
// 使用方法存储数据 localStorage.setItem("name", "wl"); // 使用属性存储数据 localStorage.book = "bc"; // 使用方法取得数据 let name = localStorage.getItem("name"); // 使用属性取得数据 let book = localStorage.book; 复制代码
两种存储方法的区别在于存储在 localStorage
中的数据会保留到通过 JS 删除或者用户清除浏览器缓存
localStorage
数据不受页面刷新影响,也不会因关闭窗口、标签页或重新启动浏览器而丢失
存储事件
每当
Storage
对象发生变化时,都会在文档上触发storage
事件
使用属性或 setItem()
设置值、使用 delete
或 removeItem()
删除值,以及每次调用 clear()
时都会触发这个事件。
这个事件的事件对象有如下 4 个属性。
domain
:存储变化对应的域key
:被设置或删除的键newValue
:键被设置的新值,若键被删除则为 null。oldValue
:键变化之前的值
代码监听 storage
事件:
window.addEventListener("storage", (event) => alert('发生变化的域:${event.domain}')); 复制代码
限制
一般来说,客户端数据的大小限制是按照每个源(协议、域和端口)来设置的,因此每个源有固定大小的数据存储空间。
大部分浏览器将
localStorage
和sessionStorage
限制为每个源 5MB
IndexedDB
Indexed Database
API 简称IndexedDB
,是浏览器中存储结构化数据的一个方案
IndexedDB 的设计几乎完全是异步的。为此,大多数操作以请求的形式执行,这些请求会异步执行,产生成功的结果或错误。
数据库
IndexedDB
是类似于MySQL
或Web SQL Database
的数据库
与传统数据库最大的区别在于,IndexedDB
使用对象存储而不是表格保存数据。IndexedDB
数据库就是在一个公共命名空间下的一组对象存储。
使用 IndexedDB
数据库的第一步是调用 indexedDB.open()
方法,并给它传入一个要打开的数据库名称。
- 如果给定名称的数据库已存在,则会发送一个打开它的请求
- 如果不存在,则会发送创建并打开这个数据库的请求
这个方法会返回 IDBRequest
的实例,可以在这个实例上添加 onerror
和onsuccess
事件处理程序。
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
事件中设置对象存储信息。
request.onupgradeneeded = (event) => { const db = event.target.result; // 如果存在则删除当前 objectStore。 if (db.objectStoreNames.contains("users")) { db.deleteObjectStore("users"); } // 设置对象存储,并指定usename为主键 db.createObjectStore("users", { keyPath: "username" }); }; 复制代码
事务
创建了对象存储之后,剩下的所有操作都是通过事务完成的。
事务要通过调用数据库对象的 transaction()
方法创建。任何时候,只要想要读取或修改数据,都要通过事务把所有修改操作组织起来。
let transaction = db.transaction("users"); 复制代码
保在事务期间只加载 users
对象存储的信息。(参数也可以是数组)。
有了事务的引用,就可以使用 objectStore()
方法并传入对象存储的名称以访问特定的对象存储。
add()/put()
:添加和更新对象get()
:获取对象delete()
:删除对象clear()
:删除所有对象
这 5 个方法都创建新的请求对象。
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
这两个事件可以用来获取事务级的状态信息:
transaction.onerror = (event) => { // 整个事务被取消 }; transaction.oncomplete = (event) => { // 整个事务成功完成 }; 复制代码
限制
IndexedDB
数据库是与页面源(协议、域和端口)绑定的,因此信息不能跨域共享
意味着www.wl.com
和bc.wl.com
会对应不同的数据存储- 每个源都有可以存储的空间限制
- 在 Chrome 正常模式下: ![](files.mdnice.com/user/24720/…=70%x)
- 在 Chrome 隐身模式下:固定 100MB 的大小
- 针对IndexDB的简化使用
在官网提供了很多基于IndexDB
包装的库,隐藏了一些比较啰嗦的数据库实例化等操作。
然后谁用了,都说好。
其他不常见的数据持久化方案
::: block-1
WebSQL
用于存储较大量数据的缓存机制。
- 将数据以数据库二维表的形式存储在客户端
- 允许SQL语句的查询
- 让浏览器实现小型数据库存储功能
- 不是H5规范
核心方法
openDatabase()
transaction()
executeSql()
已废弃并且被IndexDB
所替代 :::
::: block-1
Application Cache
允许浏览器通过manifest
配置文件在本地有选择的存储JS/CSS/图片等静态资源的文件级缓存机制
当页面不是首次打开,通过特定的manifest文件配置描述来选择本地Application Cache里的文件。
已废弃并且被ServerWorkers
所替代 :::
在昨天的文章中,我们介绍了Service Worker
,而服务工作线程的一个主要能力是可以通过编程方式实现真正的网络请求缓存机制,其中利用CacheStorage
实现该功能。
后记
分享是一种态度。
参考资料: JS高级程序设计第四版
全文完,既然看到这里了,如果觉得不错,随手点个赞和“在看”吧。