大家好,CUGGZ。
今天来分享一下常见的浏览器数据存储方案,包括 localStorage、sessionStorage、IndexedDB、Cookies。
1. 概述
现代浏览器中提供了多种存储机制,打开浏览器的控制台(Mac 可以使用 Command + Option + J 快捷键,Windows 可以使用 Control + Shift + J 快捷键)。选择 Application 选项卡,可以在 Storage中 看到 Local Storage、Session Storage、IndexedDB、Web SQL、Cookies 等:
那数据存储在浏览器中有什么使用场景呢?在以下情况下,将数据存储在浏览器中成为更可行的选择:
- 在浏览器存储中保存应用状态,比如保持用户偏好(用户特定的设置,例如亮模式或暗模式、字体大小等);
- 创建离线工作的渐进式 Web 应用,除了初始下载和更新之外没有服务器端要求;
- 缓存静态应用资源,如 HTML、CSS、JS 和图像等;
- 保存上一个浏览会话中的数据,例如存储上一个会话中的购物车内容,待办事项列表中的项目,记住用户是否以前登录过等。
无论哪种方式,将这些信息保存在客户端可以减少额外且不必要的服务器调用,并帮助提供离线支持。不过,需要注意,由于实现差异,浏览器存储机制在不同浏览器中的行为可能会有所不同。除此之外,许多浏览器已删除对 Web SQL 的支持,建议将现有用法迁移到 IndexedDB。
所以下面我们将介绍 Local Storage、Session Storage、IndexedDB、Cookies 的使用方式、使用场景以及它们之间的区别。
2. Web Storage
(1)概述
HTML5 引入了 Web Storage,这使得在浏览器中存储和检索数据变得更加容易。Web Storage API 为客户端浏览器提供了安全存储和轻松访问键值对的机制。Web Storage 提供了两个 API 来获取和设置纯字符串的键值对:
localStorage
:用于存储持久数据,除非用户手动将其从浏览器中删除,否则数据将终身存储。即使用户关闭窗口或选项卡,它也不会过期;sessionStorage
:用于存储临时会话数据,页面重新加载后仍然存在,关闭浏览器选项卡时数据丢失。
(2)方法和属性
Web Storage API 由 4 个方法 setItem()
、getItem()
、removeItem()
、clear()
、key()
和一个 length
属性组成,以 localStorage 为例:
setItem()
:用于存储数据,它有两个参数,即key
和value
。使用形式:localStorage.setItem(key, value)
;getItem()
:用于检索数据,它接受一个参数 key,即需要访问其值的键。使用形式:localStorage.getItem(key)
;removeItem()
:用于删除数据,它接受一个参数 key,即需要删除其值的键。使用形式:localStorage.removeItem(key)
;clear()
:用于清除其中存储的所有数据,使用形式:localStorage.clear()
;key()
:该方法用于获取 localStorage 中数据的所有key,它接受一个数字作为参数,该数字可以是 localStorage 项的索引位置。
console.log(typeof window.localStorage) // Object // 存储数据 localStorage.setItem("colorMode", "dark") localStorage.setItem("username", "zhangsan") localStorage.setItem("favColor", "green") console.log(localStorage.length) // 3 // 检索数据 console.log(localStorage.getItem("colorMode")) // dark // 移除数据 localStorage.removeItem("colorMode") console.log(localStorage.length) // 2 console.log(localStorage.getItem("colorMode")) // null // 检索键名 window.localStorage.key(0); // favColor // 清空本地存储 localStorage.clear() console.log(localStorage.length) // 0
localStorage 和 sessionStorage 都非常适合缓存非敏感应用数据。可以在需要存储少量简单值并不经常访问它们是使用它们。它们本质上都是同步的,并且会阻塞主 UI 线程,所以应该谨慎使用。
(3)存储事件
我们可以在浏览器上监听 localStorage 和 sessionStorage 的存储变化。 storage 事件在创建、删除或更新项目时触发。侦听器函数在事件中传递,具有以下属性:
newValue
:当在存储中创建或更新项目时传递给 setItem() 的值。 当从存储中删除项目时,此值设置为 null。oldValue
:创建新项目时,如果该键存在于存储中,则该项目的先前的值。key
:正在更改的项目的键,如果调用 .clear(),则值为 null。url
:执行存储操作的 URL。storageArea
:执行操作的存储对象(localStorage 或 sessionStorage)。
通常,我们可以使用 window.addEventListener("storage", func)
或使用 onstorage
属性(如 window.onstorage = func
)来监听 storage
事件:
window.addEventListener('storage', e => { console.log(e.key); console.log(e.oldValu); console.log(e.newValue); }); window.onstorage = e => { console.log(e.key); console.log(e.oldValu); console.log(e.newValue); });
注意,该功能不会在发生更改的同一浏览器选项卡上触发,而是由同一域的其他打开的选项卡或窗口触发。此功能用于同步同一域的所有浏览器选项卡/窗口上的数据。 因此,要对此进行测试,需要打开同一域的另一个选项卡。
(4)存储限制
localStorage.setItem('a', Array(1024 * 1024 * 5).join('a')) localStorage.setItem('b', 'a') // Uncaught DOMException: Failed to execute 'setItem' on 'Storage': Setting the value of `a` exceeded the quota.
.
在上面的例子中,收到了一个错误,首先创建了一个5MB的大字符串,当再添加其他数据时就报错了。
另外,localStorage 和 sessionStorage 只接受字符串。可以通过 JSON.stringify
和 JSON.parse
来解决这个问题:
j
const user = { name : "zhangsan", age : 28, gender : "male", profession : "lawyer" }; localStorage.setItem("user", JSON.stringify(user)); localStorage.getItem("user"); // '{"name":"zhangsan","age":28,"gender":"male","profession":"lawyer"}' JSON.parse(localStorage.getItem("user")) // {name: 'zhangsan', age: 28, gender: 'male', profession: 'lawyer'}
如果我们直接将一个对象存储在 localStorage 中,那将会在存储之前进行隐式类型转换,将对象转换为字符串,再进行存储:
const user = { name : "zhangsan", age : 28, gender : "male", profession : "lawyer" }; localStorage.setItem("user", user); localStorage.getItem("user"); // '[object Object]'
function toggle(on) { if (on) { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); } } function save(on) { localStorage.setItem('darkTheme', on.toString()); } function load() { return localStorage.getItem('darkTheme') === 'true'; } function onChange(checkbox) { const value = checkbox.checked; toggle(value); save(value); } const initialValue = load(); toggle(initialValue); document.querySelector('#darkTheme').checked = initialValue;
Web Storage 使用了同源策略,也就是说,存储的数据只能在同一来源上可用。如果域和子域相同,则可以从不同的选项卡访问 localStorage 数据,而无法访问 sessionStorage 数据,即使它是完全相同的页面。
另外:
- 无法在 web worker 或 service worker 中访问 Web Storage;
- 如果浏览器设置为隐私模式,将无法读取到 Web Storage;
- Web Storage 很容易被 XSS 攻击,敏感信息不应存储在本地存储中;
- 它是同步的,这意味着所有操作都是一次一个。对于复杂应用,它会减慢应用的运行时间。
(5)示例
下面来看一个使用 localStorage 的简单示例,使用 localStorage 来存储用户偏好:
<input type="checkbox" id="darkTheme" name="darkTheme" onclick='onChange(this);'> <label for="darkTheme">黑暗模式</label><br>
html { background: white; } .dark { background: black; color: white; }
这里的代码很简单,页面上有一个单选框,选中按钮时将页面切换为黑暗模式,并将这个配置存储在 localStorage 中。当下一次再初始页面时,获取 localStorage 中的主题设置。
3. Cookie
(1)Cookie 概述
Cookie 主要用于身份验证和用户数据持久性。Cookie 与请求一起发送到服务器,并在响应时发送到客户端;因此,cookies 数据在每次请求时都会与服务器交换。服务器可以使用 cookie 数据向用户发送个性化内容。严格来说,cookie 并不是客户端存储方式,因为服务器和浏览器都可以修改数据。它是唯一可以在一段时间后自动使数据过期的方式。
每个 HTTP 请求和响应都会发送 cookie 数据。存储过多的数据会使 HTTP 请求更加冗长,从而使应用比预期更慢:
- 浏览器限制 cookie 的大小最大为4kb,特定域允许的 cookie 数量为 20 个,并且只能包含字符串;
- cookie 的操作是同步的;
- 不能通过 web workers 来访问,但可以通过全局 window 对象访问。
Cookie 通常用于会话管理、个性化以及跨网站跟踪用户行为。我们可以通过服务端和客户端设置和访问 cookie。Cookie 还具有各种属性,这些属性决定了在何处以及如何访问和修改它们,
Cookie 分为两种类型:
- 会话 Cookie:没有指定 Expires 或 Max-Age 等属性,因此在关闭浏览器时会被删除;
- 持久性 Cookie:指定 Expires 或 Max-Age 属性。这些 cookie 在关闭浏览器时不会过期,但会在特定日期 (Expires) 或时间长度 (Max-Age) 后过期。
(2)Cookie 操作
下面先来看看如何访问和操作客户端和服务器上的 cookie。
① 客户端(浏览器)
客户端 JavaScript 可以通过 document.cookie 来读取当前位置可访问的所有 cookie。它提供了一个字符串,其中包含一个以分号分隔的 cookie 列表,使用 key=value 格式。
javascript
复制代码
document.cookie;
可以看到,在语雀主页中获取 cookie,结果中包含了登录的 cookie、语言、当前主题等。
同样,可以使用 document.cookie 来设置 cookie 的值,设置cookie也是用key=value格式的字符串,属性用分号隔开:
javascript
复制代码
document.cookie = "hello=world; domain=example.com; Secure";
这里用到了两个属性 SameSite 和 Secure,下面会介绍。如果已经存在同名的 cookie 属性,就会更新已有的属性值,如果不存在,就会创建一个新的 key=value。
如果需要经常在客户端处理 Cookie,建议使用像 js-cookie 这样的库来处理客户端 cookie:
javascript
复制代码
Cookies.set('hello', 'world', { domain: 'example.com', secure: true });
Cookies.get('hello'); // -> world
这样不仅为 cookie 上的 CRUD 操作提供了一个干净的 API,而且还支持 TypeScript,从而帮助避免属性的拼写错误。
② 服务端(Node.js)
服务端可以通过 HTTP 请求的请求头和响应头来访问和修改 cookie。每当浏览器向服务端发送 HTTP 请求时,它都会使用 cookie 头将所有相关 cookie 都附加到该站点。请求标头是一个分号分隔的字符串。
这样就可以从请求头中读取这些 cookie。如果在服务端使用 Node.js,可以像下面这样从请求对象中读取它们,将获得以分号分隔的 key=value 对:
http.createServer(function (request, response) { const cookies = request.headers.cookie; // "cookie1=value1; cookie2=value2" ... }).listen(8124);
如果想要设置 cookie,可以在响应头中添加 Set-Cookie 头,其中 cookie 采用 key=value 的格式,属性用分号分隔:
response.writeHead(200, { 'Set-Cookie': 'mycookie=test; domain=example.com; Secure' });
通常我们不会直接编写 Node.js,而是与 ExpressJS 这样的 Node.js 框架一起使用。使用 Express 可以更轻松地访问和修改 cookie。只需添加一个像 cookie-parser 这样的中间件,就可以通过 req.cookies 以 JavaScript 对象的形式获得所有的 cookie。 还可以使用 Express 内置的 res.cookie() 方法来设置 cookie:
const express = require('express') const cookieParser = require('cookie-parser') const app = express() app.use(cookieParser()) app.get('/', function (req, res) { console.log('Cookies: ', req.cookies) // Cookies: { cookie1: 'value1', cookie2: 'value2' } res.cookie('name', 'tobi', { domain: 'example.com', secure: true }) }) app.listen(8080)
聊一聊常见的浏览器数据存储方案(下)https://developer.aliyun.com/article/1411396