文章目录
JavaScript本地缓存的方法我们主要讲述以下四种:
- cookie
- sessionStorage
- localStorage
- indexedDB
cookie
Cookie,类型为「小型文本文件」,指某些网站为了辨别用户身份而储存在用户本地终端上的数据。
一般不超过 4KB 的小型文本数据,它由一个名称(Name)、一个值(Value)和其它几个用于控制 cookie有效期、安全性、使用范围的可选属性组成。
// 设置`username='jack'的cookie`
document.cookie = 'username=jack';
cookie在每次请求中都会被发送,如果不使用 HTTPS并对其加密,其保存的信息很容易被窃取,导致安全风险。
特点
-
不设置过期时间(
cookie保存在内存里
):
则cookie生命周期为浏览器会话期间,只要关闭浏览器窗口,cookie就消失了
。
这种生命期为浏览会话期的cookie被称为会话cookie
。 -
设置了过期时间(
浏览器就会把cookie保存到硬盘上
):
则cookie生命周期为只在设置的cookie过期时间之前一直有效
。
关闭后再次打开浏览器,这些cookie依然有效直到超过设定的过期时间。
存储在硬盘上的cookie可以在不同的浏览器进程间共享,比如两个IE窗口。而对于保存在内存的cookie,不同的浏览器有不同的处理方式。
.
缺点
cookie
存放数据大小为4K左右, 且有个数限制(各浏览器不同),一般不能超过20个。不能储存大数据且不易读取。
而localStorage
和sessionStorage
能存放5M或者更大。http请求
时需要发送到服务端,增加了请求的数据量
.
用法
创建或修改cookie:
document.cookie = "key=value; expires=date; path=path; domain=domain; secure";
- 名称和值
key=value
:要设置的cookie的名称和值。 - 有效期
expires=date
:可选,cookie的过期日期。如果不设置,则cookie在浏览器关闭时过期。 - 路径
path=path
:可选,指定哪些路径下的页面可以访问该cookie。默认为设置cookie的页面所在的路径。 - 域
domain=domain
:可选,指定哪些域名下的页面可以访问该cookie。默认为设置cookie的页面的域名。 - HTTPS传输
secure
:可选,如果设置了该属性,则只能通过HTTPS协议来传输cookie。
例如,设置一个名为 username
、值为 猫老板
、过期时间为 7天
后的 cookie:
document.cookie = "username=猫老板; expires=" + new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toUTCString();
.
⚡️ Set-Cookie 响应头
字段名 | 类别 | 描述 | 示例 |
---|---|---|---|
Name | 常见 | Cookie的名称 | UserID |
Value | 常见 | Cookie的值 | 123456 |
Expires | 有效期 | Cookie的过期日期/时间 | Expires=Wed, 21 Oct 2023 07:28:00 GMT |
Max-Age | 有效期 | 定义Cookie生命周期的秒数(如果设置,将覆盖Expires) | Max-Age=86400 (1天) |
Domain | 作用域 | 限制哪些域可以接收Cookie | Domain=example.com |
Path | 作用域 | 限制哪些路径下的页面可以接收Cookie | Path=/users |
Secure | 安全性 | 限制Cookie只能通过HTTPS传输 | Secure |
HttpOnly | 安全性 | 限制JavaScript访问(即无法通过document.cookie访问) | HttpOnly |
SameSite | 安全性 | 控制Cookie的跨站策略 | SameSite=Lax 或 SameSite=Strict 或 SameSite=None |
Web Storage
Web Storage 是一种在客户端存储数据的 Web API,它提供了两种在浏览器中存储数据的方式:localStorage
和 sessionStorage
。
这两种存储方式都使得数据可以在用户的浏览器会话中持久保存,但与传统的 cookies 相比,它们提供了更大的存储容量(通常可以达到数兆字节)和更好的性能。
localStorage
localStorage 的生命周期是永久的,关闭页面或浏览器 localStorage 中的数据也不会消失。localStorage 除非主动删除数据,否则数据永远不会消失。
特点
- 生命周期:持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的
- 存储的信息在同一域中是共享的
- 当本页操作(新增、修改、删除)了localStorage的时候,本页面不会触发storage事件,但是别的页面会触发storage事件。
- 大小:5M(跟浏览器厂商有关系)
- localStorage本质上是对字符串的读取,如果存储内容多的话会消耗内存空间,会导致页面变卡
- 受同源策略的限制
缺点
- 无法像Cookie一样设置过期时间
- 浏览器的大小不统一,并且在 IE8 以上的 IE 版本才支持 localStorage 这个属性。
- 只能存入字符串,无法直接存对象,这个在对我们日常比较常见的JSON对象类型需要一些转换。
- 在浏览器的隐私模式下面是不可读取的。
- 本质上是对字符串的读取,如果存储内容多的话会消耗内存空间,会导致页面变卡。
- 不能被爬虫抓取到。
用法
// 设置
localStorage.setItem('username','猫老板');
// 获取
localStorage.getItem('username');
// 获取键名
localStorage.key(0) //获取第一个键名
// 删除
localStorage.removeItem('username');
// 清除所有存储
localStorage.clear()
.
sessionStorage
sessionStorage和 localStorage使用方法基本一致,唯一不同的是生命周期。
sessionStorage 的生命周期是仅在当前会话下有效,关闭页面或浏览器 sessionStorage 中的数据会被销毁清空。
sessionStorage 引入了一个“浏览器窗口”的概念,sessionStorage 是在同源的窗口中始终存在的数据。只要这个浏览器窗口没有关闭,即使刷新页面或者进入同源另一个页面,数据依然存在。同时独立的打开同一个窗口同一个页面,sessionStorage 也是不一样的(不同浏览器、同一个页面也是不共享的)。
indexedDB
indexedDB
是一种低级API,用于客户端存储大量结构化数据(包括, 文件/ blobs)。该API使用索引来实现对该数据的高性能搜索
虽然 Web Storage对于存储较少量的数据很有用,但对于存储更大量的结构化数据来说,这种方法不太有用。IndexedDB提供了一个解决方案
优点
- 储存量理论上没有上限
- 所有操作都是异步的,相比 LocalStorage 同步操作性能更高,尤其是数据量较大时
- 原生支持储存JS的对象
- 是个正经的数据库,意味着数据库能干的事它都能干
缺点
- 操作非常繁琐
- 本身有一定门槛
用法
-
打开数据库并且开始一个事务
-
创建一个 object store
-
构建一个请求来执行一些数据库操作,像增加或提取数据等。
-
通过监听正确类型的 DOM 事件以等待操作完成。
-
在操作结果上进行一些操作(可以在 request对象中找到)
以下是一个简单的 IndexedDB 用法示例,该示例展示了如何打开一个数据库,创建一个对象存储(object store),并添加、检索和删除数据。
const dbName = 'myDatabase'; // 数据库名称
const dbVersion = 1; // 数据库版本
// 打开数据库
const request = indexedDB.open(dbName, dbVersion);
request.onerror = function(event) {
console.error("Database error: " + event.target.errorCode);
};
request.onsuccess = function(event) {
// 获取数据库对象
const db = event.target.result;
// 创建一个对象存储(如果不存在)
if (!db.objectStoreNames.contains('myObjectStore')) {
db.createObjectStore('myObjectStore', { keyPath: 'id' });
}
// 示例:添加数据
const transaction = db.transaction(['myObjectStore'], 'readwrite');
const store = transaction.objectStore('myObjectStore');
store.put({ id: 1, name: 'Alice', age: 25 });
store.put({ id: 2, name: 'Bob', age: 30 });
transaction.oncomplete = function(event) {
console.log('Data added successfully');
// 检索数据
const tx = db.transaction(['myObjectStore'], 'readonly');
const store = tx.objectStore('myObjectStore');
// 获取所有记录
const getAllRequest = store.getAll();
getAllRequest.onsuccess = function(event) {
console.log('Retrieved data:', event.target.result);
};
// 获取指定记录
const getRequest = store.get(1);
getRequest.onsuccess = function(event) {
console.log('Retrieved specific data:', event.target.result);
};
// 删除数据
const deleteRequest = store.delete(2);
deleteRequest.onsuccess = function(event) {
console.log('Data deleted successfully');
};
};
transaction.onerror = function(event) {
console.error('Transaction error: ' + event.target.errorCode);
};
};
request.onupgradeneeded = function(event) {
// 当数据库版本发生变化时,这个事件会被触发
// 在这里可以修改或删除现有的对象存储,或者创建新的对象存储
const db = event.target.result;
// ...
};
三方库(Godb.js)
关于使用indexdb的使用会比较繁琐,大家可以通过使用 Godb.js 库进行缓存,最大化的降低操作难度
安装:npm install godb
操作非常简单,增、删、改、查各只需要一行代码:
import GoDB from 'godb';
const testDB = new GoDB('testDB');
const user = testDB.table('user');
const data = {
name: 'luke',
age: 22
};
user.add(data) // 增
.then(luke => user.get(luke.id)) // 查,等价于 user.get({ id: luke.id })
.then(luke => user.put({ ...luke, age: 23 })) // 改
.then(luke => user.delete(luke.id)); // 删
⚡️ 区别
特性 | cookie | sessionStorage | localStorage | indexedDB |
---|---|---|---|---|
数据存储位置 | 客户端浏览器,随HTTP请求发送到服务器 | 客户端浏览器,当前会话期间有效 | 客户端浏览器,永久存储 | 客户端浏览器,持久化存储 |
数据大小限制 | 约4KB(具体取决于浏览器) | 5MB(或更大,取决于浏览器) | 5MB(或更大,取决于浏览器) | 几乎没有限制 |
数据生命周期 | 可设置过期时间,过期后自动删除 | 会话结束(浏览器关闭或标签页关闭) | 永久存储,除非手动删除 | 永久存储,除非手动删除 |
数据类型 | 字符串 | 键值对,任何数据类型(对象会被转换为字符串) | 键值对,任何数据类型(对象会被转换为字符串) | 复杂的结构化数据,支持索引 |
访问方式 | 服务器端和客户端均可访问 | 仅客户端JavaScript可访问 | 仅客户端JavaScript可访问 | 仅客户端JavaScript可访问 |
安全性 | 安全性较低,可通过HTTP请求发送 | 安全性较高,不会随HTTP请求发送 | 安全性较高,不会随HTTP请求发送 | 安全性较高,不会随HTTP请求发送 |
同步/异步 | 同步(但不建议在服务器端同步使用) | 同步 | 同步 | 异步 |
搜索功能 | 不支持 | 不支持 | 不支持 | 支持 |
跨域共享 | 可通过服务器设置跨域访问 | 不支持跨域共享 | 不支持跨域共享 | 不支持跨域共享(但可通过其他机制如postMessage实现) |
应用场景 | 标记用户与跟踪用户行为的情况 | 敏感账号一次性登录 | 适合长期保存在本地的数据(令牌) | 存储大量数据的情况、在线文档(富文本编辑器)保存编辑历史的情况 |
单点登录 SSO
在同域下的客户端/服务端认证系统中,通过客户端携带凭证,维持一段时间内的登录状态。
但当我们业务线越来越多,就会有更多业务系统分散到不同域名下,就需要「一次登录,全线通用」的能力,叫做「单点登录」。
-
主域名相同的单点登录:
如果业务系统都在同一主域名下,比如
wenku.baidu.com
和tieba.baidu.com
,就好办了。可以直接把
cookie domain
设置为主域名.baidu.com
,百度也就是这么干的。
. -
主域名不同的单点登录:
比如滴滴公司,同时拥有
didichuxing.com
、xiaojukeji.com
、didiglobal.com
等域名,这三种 cookie 是完全绕不开的。这要能实现「一次登录,全线通用」,才是真正的单点登录。
这种场景下,我们需要独立的认证服务,通常被称为 SSO。
以下是关键步骤:
-
用户进入 A 系统,若没有登录凭证(ticket:如session ID或token),A 系统将用户重定向到 SSO 服务器进行身份验证
-
SSO 没登录过,也就没有 SSO 系统下没有凭证,则用户需要输入账号密码进行登录
-
SSO 账号密码验证成功,通过接口返回做两件事:
- 保存登录凭证,记录用户在 SSO 登录状态
- SSO服务器会发送一个授权令牌(如JWT)或票据(ticket)给客户端,并可能设置一个cookie在用户的浏览器中,以便后续识别用户的登录状态。
.
-
客户端拿到 ticket,保存起来,并带着 ticket 请求系统 A 接口
-
A 系统校验 ticket,成功后正常处理业务请求
-
此时用户第一次进入 B 系统,若没有登录凭证(ticket),B 系统将用户重定向到 SSO 服务器进行身份验证
-
由于用户之前在 SSO 服务器上登录过(有登录凭证),并且会话仍然有效,不用再次登录,只需要生成一个新的 ticket(或者重用已有的 ticket ),并将其发送给客户端。
-
客户端拿到 ticket,保存起来,带着请求系统 B 接口
-
B 系统校验 ticket,成功后正常处理业务请求
这个流程描述了用户如何通过 SSO 系统登录并在多个系统(A 系统和 B 系统)之间无缝切换,而无需在每个系统上都输入用户名和密码。
这是单点登录系统的主要优势之一,提高了用户体验并降低了管理多个账户和密码的复杂性。