Browser storage 看起来简单,直到它成为 authentication flow、multi-tab 体验,或必须撑过 schema changes 的应用的一部分。
- API 很少是困难之处。真正的决策是 谁需要这些数据、它应该存活多久,以及它跨越了哪条安全边界。
- 浏览器并没有名为
cookieStorage的标准 API。传统 cookies 走document.cookie;较新的异步 Cookie Store API 走cookieStore。它们管理的是同一层底层 cookies,两者的行为都不像 Web Storage。
概览
| Property | localStorage | sessionStorage | Cookies |
|---|---|---|---|
| Typical lifetime | Until explicitly cleared or evicted | Current tab's page session | Session-based or until their expiry date |
| Sent to server | No | No | Yes, on matching requests |
| JavaScript access | Yes, synchronously | Yes, synchronously | Yes, unless marked HttpOnly |
| Best suited for | Persistent, non-sensitive preferences | Temporary state that should survive reloads | Server-managed sessions and small server-readable values |
- Web Storage 属于 client。Cookies 参与 HTTP request lifecycle。
- 谁都不该被当成受信任的 database。
localStorage
localStorage 把数据为某个 origin 持久保存,撑过 page reloads 与 browser restarts。
ts
const preferences = {
theme: "dark",
density: "compact",
}
localStorage.setItem("preferences:v1", JSON.stringify(preferences))
const stored = localStorage.getItem("preferences:v1")
const parsed = stored ? JSON.parse(stored) : nullts
type Preferences = {
theme: "light" | "dark"
}
function readPreferences(): Preferences | null {
try {
const value = JSON.parse(localStorage.getItem("preferences:v1") ?? "null")
if (value?.theme === "light" || value?.theme === "dark") {
return value
}
} catch {
// Corrupt or manually edited storage should not break the application.
}
return null
}- 适合少量、非敏感 preferences:theme、已关闭的 notice、未完成的 local draft。
- Values 是 strings,访问是 synchronous,每一次 read 或 write 都会挡住 main thread。Capacity 与 eviction 因 browser 而异。它是小型 persistence 机制,不是 database。
- Version keys,并在读取时 validate:已部署的应用会变,旧的 browser 数据还在。
- Failure: 把 session tokens、passwords,或长期 bearer tokens 放进
localStorage。该 origin 上的任何 script 都能读它,包括 XSS。偷走这个值,就足以在别处重用。
sessionStorage
sessionStorage 有类似的 string-based API,但它的 lifetime 绑在 page session。
ts
sessionStorage.setItem(
"checkout:draft",
JSON.stringify({ step: 2, deliveryMethod: "pickup" })
)- 它能撑过同一 tab 里的 reloads,通常在该 tab 或 window 关闭时被清除。分开的 tabs 得到分开的 storage。
- 适合临时 UI state:multi-step form、return URL,或应撑过一次意外 refresh、却不该变成永久 preference 的 filters。
- Failure: 把
sessionStorage当成安全边界。页面上的 scripts 仍然能读它,用户也可以用复制或恢复 tabs 的方式,让 lifetime 假设变得没那么明显。它适合的是 product lifetime 与数据匹配的时候,而不是因为它比localStorage更安全。
Cookies 与 Cookie Store API
Cookies 不同,因为 browser 可以把它们附在 HTTP requests 上。
http
Set-Cookie: session=opaque-value; Path=/; HttpOnly; Secure; SameSite=Laxts
const preference = await cookieStore.get("theme")
await cookieStore.set({
name: "theme",
value: "dark",
path: "/",
sameSite: "lax",
})- 适合 server 需要这个值的时候,尤其是 server-managed sessions。Security-sensitive cookies 通常应由 server 创建。
HttpOnly阻止 JavaScript 读取 cookie,Secure把它限制在 HTTPS,SameSite有助于限制 CSRF。它们降低风险;它们不能取代 output escaping、CSP、CSRF analysis,或 session expiration 与 rotation。- 传统
document.cookie是 synchronous 的,解析也很别扭。Cookie Store API 在支持的地方是 asynchronous。Client-side code 仍然无法通过cookieStore读取HttpOnlycookie——这正是限制的意义。 - Failure: 因为
cookieStore看起来像localStorage,就把 preferences 塞进 cookies。Cookies 很小,domain 与 path 规则很微妙,而且随 requests 带上的 cookies 每次都会增加网络开销。
默认选择
以最短的有用 lifetime,存放最少的数据。
- 当数据只需在当前页面打开期间存活时,使用 in-memory state。
- 当临时 state 应在单一 tab 中撑过 reloads 时,使用 sessionStorage。
- 对应需要跨多次访问持久保存的少量、非敏感 preferences,使用 localStorage。
- 当 server 必须收到该值时使用 cookies,尤其是放在 secure、HTTP-only cookie 中的 opaque session identifier。
- 当应用需要大量数据、结构化 records、transactions,或 offline 行为时,改用 IndexedDB。
- Browser storage 由用户控制,可被清除或修改,绝不应被当成权威的 source of truth。读取时验证、处理 storage failures,并让 server-side authorization 独立于 client 可编辑的任何内容。
选择 storage 是架构决策,而不是 API 偏好。从数据的 lifetime 与信任等级出发;正确的 browser primitive 通常会随之而来。
Recap Q&A
阅读下一篇笔记
JavaScript 核心概念