浏览器提供了多种本地存储方案,用于在客户端保存数据。了解它们的特点和使用场景,才能选择合适的存储方式。
🎯 存储方案对比
| 特性 | localStorage | sessionStorage | Cookie |
|---|
| 容量 | 约 5MB | 约 5MB | 约 4KB |
| 生命周期 | 永久 | 标签页关闭 | 可设置过期时间 |
| 请求携带 | 否 | 否 | 自动携带 |
| 作用域 | 同源共享 | 仅当前标签页 | 可设置路径和域 |
| API | 简单 | 简单 | 较复杂 |
localStorage
基本操作
localStorage.setItem('username', '张三')
const username = localStorage.getItem('username')
console.log(username) // '张三'
localStorage.removeItem('username')
console.log(localStorage.length)
console.log(localStorage.key(0))
存储对象
// 🔶 localStorage 只能存储字符串
localStorage.setItem('user', { name: '张三' })
localStorage.getItem('user') // '[object Object]'(错误)
const user = { name: '张三', age: 25 }
localStorage.setItem('user', JSON.stringify(user))
const stored = localStorage.getItem('user')
const parsedUser = JSON.parse(stored)
console.log(parsedUser.name) // '张三'
封装工具类
set(key, value, expires) {
expires: expires ? Date.now() + expires : null,
localStorage.setItem(key, JSON.stringify(data))
get(key, defaultValue = null) {
const item = localStorage.getItem(key)
if (!item) return defaultValue
const data = JSON.parse(item)
if (data.expires && Date.now() > data.expires) {
localStorage.removeItem(key)
localStorage.removeItem(key)
return localStorage.getItem(key) !== null
storage.set('token', 'abc123', 3600000) // 1小时后过期
storage.get('token') // 'abc123'
监听存储变化
window.addEventListener('storage', (event) => {
console.log('键:', event.key)
console.log('旧值:', event.oldValue)
console.log('新值:', event.newValue)
console.log('来源:', event.url)
function broadcast(type, data) {
localStorage.removeItem('broadcast')
window.addEventListener('storage', (event) => {
if (event.key === 'broadcast' && event.newValue) {
const { type, data } = JSON.parse(event.newValue)
handleBroadcast(type, data)
sessionStorage
基本用法
sessionStorage.setItem('tempData', 'value')
sessionStorage.getItem('tempData')
sessionStorage.removeItem('tempData')
使用场景
const form = document.getElementById('myForm')
form.addEventListener('input', () => {
const formData = new FormData(form)
const data = Object.fromEntries(formData)
sessionStorage.setItem('formDraft', JSON.stringify(data))
const draft = sessionStorage.getItem('formDraft')
const data = JSON.parse(draft)
Object.entries(data).forEach(([name, value]) => {
const input = form.elements[name]
if (input) input.value = value
form.addEventListener('submit', () => {
sessionStorage.removeItem('formDraft')
sessionStorage.setItem('selectedItem', JSON.stringify({ id: 1, name: '商品' }))
location.href = '/detail'
const item = JSON.parse(sessionStorage.getItem('selectedItem'))
if (!sessionStorage.getItem('welcomeShown')) {
sessionStorage.setItem('welcomeShown', 'true')
Cookie
读取 Cookie
// 'name=张三; token=abc123; theme=dark'
function getCookie(name) {
const cookies = document.cookie.split('; ')
for (const cookie of cookies) {
const [key, value] = cookie.split('=')
return decodeURIComponent(value)
getCookie('name') // '张三'
function getAllCookies() {
return document.cookie.split('; ').reduce((obj, cookie) => {
const [key, value] = cookie.split('=')
obj[key] = decodeURIComponent(value)
设置 Cookie
document.cookie = 'name=张三'
const expires = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) // 7天后
document.cookie = `name=张三; expires=${expires.toUTCString()}`
document.cookie = 'name=张三; max-age=604800' // 7天
document.cookie = 'name=张三; path=/'
document.cookie = 'name=张三; domain=.example.com'
document.cookie = 'token=abc; secure' // 仅 HTTPS
document.cookie = 'token=abc; httpOnly' // 仅服务器可访问(JS 无法设置)
document.cookie = 'token=abc; samesite=strict' // 防止 CSRF
function setCookie(name, value, options = {}) {
let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`
cookie += `; expires=${expires.toUTCString()}`
if (maxAge !== undefined) {
cookie += `; max-age=${maxAge}`
cookie += `; path=${path}`
cookie += `; domain=${domain}`
cookie += `; samesite=${sameSite}`
setCookie('user', 'zhangsan', { maxAge: 86400 }) // 1天
删除 Cookie
function deleteCookie(name, path = '/') {
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=${path}`
document.cookie = 'name=; max-age=0; path=/'
Cookie 工具类
const cookies = document.cookie.split('; ')
for (const c of cookies) {
const [key, value] = c.split('=')
return decodeURIComponent(value)
set(name, value, days = 7) {
const maxAge = days * 24 * 60 * 60
document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}; max-age=${maxAge}; path=/; samesite=lax`
document.cookie = `${name}=; max-age=0; path=/`
return this.get(name) !== null
cookie.set('theme', 'dark', 30) // 30天
cookie.get('theme') // 'dark'
IndexedDB(简介)
const request = indexedDB.open('MyDatabase', 1)
request.onerror = () => console.error('数据库打开失败')
request.onupgradeneeded = (event) => {
const db = event.target.result
if (!db.objectStoreNames.contains('users')) {
db.createObjectStore('users', { keyPath: 'id' })
request.onsuccess = (event) => {
const db = event.target.result
const transaction = db.transaction(['users'], 'readwrite')
const store = transaction.objectStore('users')
store.add({ id: 1, name: '张三', age: 25 })
const getRequest = store.get(1)
getRequest.onsuccess = () => {
console.log(getRequest.result)
// 更推荐使用封装库如 idb、Dexie.js
实际应用
用户偏好设置
const stored = localStorage.getItem('preferences')
return stored ? { ...this.defaults, ...JSON.parse(stored) } : this.defaults
localStorage.setItem('preferences', JSON.stringify(prefs))
const current = this.load()
localStorage.removeItem('preferences')
preferences.set('theme', 'dark')
preferences.get('theme') // 'dark'
登录状态管理
setToken(token, remember = false) {
localStorage.setItem('authToken', token)
sessionStorage.setItem('authToken', token)
localStorage.getItem('authToken') || sessionStorage.getItem('authToken')
localStorage.removeItem('authToken')
sessionStorage.removeItem('authToken')
document.cookie = 'authToken=; max-age=0; path=/'
缓存 API 数据
const item = localStorage.getItem(`cache_${key}`)
const { data, timestamp, maxAge } = JSON.parse(item)
if (Date.now() - timestamp > maxAge) {
localStorage.removeItem(`cache_${key}`)
set(key, data, maxAge = 300000) {
async fetch(url, options = {}) {
const cached = this.get(cacheKey)
const response = await fetch(url)
const data = await response.json()
this.set(cacheKey, data, options.maxAge)
Object.keys(localStorage)
.filter((key) => key.startsWith('cache_'))
.forEach((key) => localStorage.removeItem(key))
const users = await apiCache.fetch('/api/users', { maxAge: 60000 })
存储容量检测
function getStorageSize() {
for (const key in localStorage) {
if (localStorage.hasOwnProperty(key)) {
total += localStorage.getItem(key).length * 2 // UTF-16
usedMB: (total / 1024 / 1024).toFixed(2),
available: 5 * 1024 * 1024 - total, // 假设 5MB 限制
function safeSetItem(key, value) {
localStorage.setItem(key, value)
if (e.name === 'QuotaExceededError') {
安全注意事项
// 密码、信用卡号等不应存��在 localStorage
// localStorage 可被 JavaScript 访问
// 如果网站有 XSS 漏洞,攻击者可以读取存储数据
// - 短期 token:sessionStorage
// - 长期 token:httpOnly Cookie(更安全)
function getStoredData(key, validate) {
const data = localStorage.getItem(key)
const parsed = JSON.parse(data)
if (validate && !validate(parsed)) {
localStorage.removeItem(key)
localStorage.removeItem(key)
const user = getStoredData('user', (data) => {
return data && typeof data.id === 'number' && typeof data.name === 'string'
总结
| 场景 | 推荐方案 |
|---|
| 用户偏好设置 | localStorage |
| 登录状态(记住我) | localStorage + Cookie |
| 登录状态(不记住) | sessionStorage |
| 表单临时数据 | sessionStorage |
| API 缓存 | localStorage(带过期时间) |
| 敏感数据 | httpOnly Cookie |
| 大量结构化数据 | IndexedDB |
| 方法 | localStorage / sessionStorage |
|---|
| 存储 | setItem(key, value) |
| 读取 | getItem(key) |
| 删除 | removeItem(key) |
| 清空 | clear() |
| 数量 | length |
核心要点:
- localStorage 数据永久保存,sessionStorage 关闭标签页清除
- 只能存储字符串,对象需要 JSON 序列化
- Cookie 会随请求自动发送,适合身份验证
- 注意存储容量限制和安全问题
- 敏感数据使用 httpOnly Cookie