本地存储 #
什么是本地存储? #
本地存储(localStorage)是 Web Storage API 的一部分,允许在浏览器中存储键值对数据。
text
┌─────────────────────────────────────────────────────────────┐
│ 浏览器存储方式对比 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Cookie │ │ localStorage│ │sessionStorage│ │
│ ├─────────────┤ ├─────────────┤ ├─────────────┤ │
│ │ 大小: 4KB │ │ 大小: 5MB │ │ 大小: 5MB │ │
│ │ 过期: 可设置 │ │ 过期: 永久 │ │ 过期: 会话 │ │
│ │ 请求: 自动 │ │ 请求: 不携带 │ │ 请求: 不携带 │ │
│ │ 跨域: 受限 │ │ 跨域: 同源 │ │ 跨域: 同源 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ IndexedDB │ │ Web SQL │ │ Cache API │ │
│ ├─────────────┤ ├─────────────┤ ├─────────────┤ │
│ │ 大小: 无限 │ │ 大小: 无限 │ │ 大小: 无限 │ │
│ │ 类型: NoSQL │ │ 类型: SQL │ │ 类型: 缓存 │ │
│ │ 异步: 是 │ │ 异步: 是 │ │ 异步: 是 │ │
│ │ 状态: 推荐 │ │ 状态: 废弃 │ │ 状态: 推荐 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
localStorage 特点 #
text
┌─────────────────────────────────────────────────────────────┐
│ localStorage 特点 │
├─────────────────────────────────────────────────────────────┤
│ ✅ 存储容量大:约 5MB │
│ ✅ 持久化存储:除非手动清除,否则永久保存 │
│ ✅ 同源策略:相同协议、域名、端口才能访问 │
│ ✅ 同步操作:API 简单,使用方便 │
│ ✅ 仅客户端:不会随请求发送到服务器 │
└─────────────────────────────────────────────────────────────┘
基础函数 #
store 模块 #
封装了 localStorage 的增删改查操作,支持过期时间。
javascript
const _toString = val => Object.prototype.toString.call(val).slice(8, -1);
const isString = val => _toString(val) === 'String';
const isNumber = val => _toString(val) === 'Number';
const isDate = val => _toString(val) === 'Date';
const getRealTime = (time) => {
let dateTime = null;
switch (true) {
case isString(time):
dateTime = new Date(parseInt(time));
break;
case isNumber(time):
dateTime = new Date(time);
break;
case isDate(time):
dateTime = time;
break;
}
return dateTime;
};
const store = {
get(key) {
if (key === undefined) return null;
let value = localStorage.getItem(key);
try {
value = JSON.parse(value) || {};
} catch {
value = {};
}
const { expires = '', data = null } = value;
const _expires = getRealTime(expires);
if (_expires && _expires < new Date()) {
store.remove(key);
return null;
}
return data;
},
set(key, value, expires) {
if (key === undefined) throw new Error('key can not be undefined');
const _expires = getRealTime(expires);
if (!_expires) {
localStorage.setItem(key, JSON.stringify({
data: value
}));
return;
}
if (value === undefined || value === '' || value === null ||
value !== value || _expires.getTime() < Date.now()) {
store.remove(key);
return;
}
localStorage.setItem(key, JSON.stringify({
expires: _expires.getTime(),
data: value
}));
},
remove(key) {
localStorage.removeItem(key);
},
removeAll() {
localStorage.clear();
}
};
export default store;
基本使用 #
javascript
import store from './utils/store';
store.set('user', { name: 'John', age: 25 });
const user = store.get('user');
console.log(user);
store.remove('user');
store.removeAll();
设置过期时间 #
javascript
import store from './utils/store';
const oneHour = Date.now() + 60 * 60 * 1000;
store.set('session', { token: 'abc123' }, oneHour);
const oneDay = Date.now() + 24 * 60 * 60 * 1000;
store.set('cache', { data: [1, 2, 3] }, oneDay);
const oneWeek = Date.now() + 7 * 24 * 60 * 60 * 1000;
store.set('preferences', { theme: 'dark' }, oneWeek);
进阶用法 #
存储容量检测 #
javascript
const getStorageSize = () => {
let total = 0;
for (let key in localStorage) {
if (localStorage.hasOwnProperty(key)) {
total += localStorage.getItem(key).length * 2;
}
}
return {
bytes: total,
kb: (total / 1024).toFixed(2),
mb: (total / 1024 / 1024).toFixed(4)
};
};
const isStorageAvailable = (size = 1024 * 1024) => {
try {
const test = 'x'.repeat(size);
localStorage.setItem('__test__', test);
localStorage.removeItem('__test__');
return true;
} catch (e) {
return false;
}
};
console.log(getStorageSize());
console.log(isStorageAvailable());
存储事件监听 #
javascript
const onStorageChange = (callback) => {
window.addEventListener('storage', (e) => {
callback({
key: e.key,
oldValue: e.oldValue,
newValue: e.newValue,
url: e.url
});
});
};
onStorageChange((change) => {
console.log('存储变化:', change);
});
批量操作 #
javascript
const storeMultiple = (items) => {
Object.entries(items).forEach(([key, value]) => {
store.set(key, value);
});
};
const getMultiple = (keys) => {
return keys.reduce((result, key) => {
result[key] = store.get(key);
return result;
}, {});
};
const removeMultiple = (keys) => {
keys.forEach(key => store.remove(key));
};
storeMultiple({
user: { name: 'John' },
settings: { theme: 'dark' },
token: 'abc123'
});
const data = getMultiple(['user', 'settings', 'token']);
console.log(data);
removeMultiple(['user', 'token']);
命名空间存储 #
javascript
class NamespacedStore {
constructor(namespace) {
this.namespace = namespace;
}
getKey(key) {
return `${this.namespace}:${key}`;
}
get(key) {
return store.get(this.getKey(key));
}
set(key, value, expires) {
return store.set(this.getKey(key), value, expires);
}
remove(key) {
return store.remove(this.getKey(key));
}
clear() {
const keys = Object.keys(localStorage)
.filter(k => k.startsWith(`${this.namespace}:`));
keys.forEach(k => localStorage.removeItem(k));
}
}
const userStore = new NamespacedStore('user');
const appStore = new NamespacedStore('app');
userStore.set('profile', { name: 'John' });
appStore.set('config', { theme: 'dark' });
console.log(userStore.get('profile'));
console.log(appStore.get('config'));
实际应用场景 #
1. 用户登录状态 #
javascript
import store from './utils/store';
class AuthStorage {
constructor() {
this.tokenKey = 'auth_token';
this.userKey = 'auth_user';
}
setToken(token, expiresDays = 7) {
const expires = Date.now() + expiresDays * 24 * 60 * 60 * 1000;
store.set(this.tokenKey, token, expires);
}
getToken() {
return store.get(this.tokenKey);
}
setUser(user, expiresDays = 7) {
const expires = Date.now() + expiresDays * 24 * 60 * 60 * 1000;
store.set(this.userKey, user, expires);
}
getUser() {
return store.get(this.userKey);
}
clear() {
store.remove(this.tokenKey);
store.remove(this.userKey);
}
isLoggedIn() {
return !!this.getToken();
}
}
const authStorage = new AuthStorage();
authStorage.setToken('abc123xyz', 7);
authStorage.setUser({ id: 1, name: 'John' }, 7);
2. 数据缓存 #
javascript
import store from './utils/store';
class CacheManager {
constructor(prefix = 'cache') {
this.prefix = prefix;
}
getKey(key) {
return `${this.prefix}:${key}`;
}
async getOrSet(key, fetcher, ttl = 3600000) {
const cacheKey = this.getKey(key);
const cached = store.get(cacheKey);
if (cached !== null) {
console.log('缓存命中:', key);
return cached;
}
console.log('缓存未命中,获取数据:', key);
const data = await fetcher();
const expires = Date.now() + ttl;
store.set(cacheKey, data, expires);
return data;
}
invalidate(key) {
store.remove(this.getKey(key));
}
invalidateAll() {
Object.keys(localStorage)
.filter(k => k.startsWith(`${this.prefix}:`))
.forEach(k => localStorage.removeItem(k));
}
}
const cache = new CacheManager('api');
const userData = await cache.getOrSet(
'user:123',
() => fetch('/api/user/123').then(r => r.json()),
5 * 60 * 1000
);
3. 表单数据持久化 #
javascript
import store from './utils/store';
class FormPersistence {
constructor(formId, options = {}) {
this.formId = formId;
this.form = document.getElementById(formId);
this.key = `form:${formId}`;
this.autoSave = options.autoSave ?? true;
this.ttl = options.ttl ?? 24 * 60 * 60 * 1000;
if (this.autoSave) {
this.enableAutoSave();
}
}
save() {
const formData = new FormData(this.form);
const data = {};
formData.forEach((value, key) => {
data[key] = value;
});
store.set(this.key, data, Date.now() + this.ttl);
}
restore() {
const data = store.get(this.key);
if (!data) return false;
Object.entries(data).forEach(([key, value]) => {
const input = this.form.querySelector(`[name="${key}"]`);
if (input) {
if (input.type === 'checkbox') {
input.checked = value === 'on';
} else if (input.type === 'radio') {
const radio = this.form.querySelector(`[name="${key}"][value="${value}"]`);
if (radio) radio.checked = true;
} else {
input.value = value;
}
}
});
return true;
}
clear() {
store.remove(this.key);
}
enableAutoSave() {
this.form.addEventListener('input', () => this.save());
this.form.addEventListener('change', () => this.save());
}
disableAutoSave() {
this.form.removeEventListener('input', () => this.save());
this.form.removeEventListener('change', () => this.save());
}
}
const formPersistence = new FormPersistence('myForm', {
autoSave: true,
ttl: 7 * 24 * 60 * 60 * 1000
});
formPersistence.restore();
4. 购物车 #
javascript
import store from './utils/store';
class ShoppingCart {
constructor() {
this.key = 'shopping_cart';
}
getCart() {
return store.get(this.key) || [];
}
addItem(item) {
const cart = this.getCart();
const existingIndex = cart.findIndex(i => i.id === item.id);
if (existingIndex > -1) {
cart[existingIndex].quantity += item.quantity || 1;
} else {
cart.push({ ...item, quantity: item.quantity || 1 });
}
store.set(this.key, cart);
return cart;
}
removeItem(itemId) {
const cart = this.getCart().filter(item => item.id !== itemId);
store.set(this.key, cart);
return cart;
}
updateQuantity(itemId, quantity) {
const cart = this.getCart();
const item = cart.find(i => i.id === itemId);
if (item) {
item.quantity = quantity;
if (item.quantity <= 0) {
return this.removeItem(itemId);
}
}
store.set(this.key, cart);
return cart;
}
getTotal() {
const cart = this.getCart();
return cart.reduce((total, item) => total + item.price * item.quantity, 0);
}
clear() {
store.remove(this.key);
}
}
const cart = new ShoppingCart();
cart.addItem({ id: 1, name: '商品A', price: 99.9, quantity: 2 });
cart.addItem({ id: 2, name: '商品B', price: 199.9 });
console.log(cart.getCart());
console.log('总价:', cart.getTotal());
错误处理 #
存储异常处理 #
javascript
const safeStore = {
get(key) {
try {
return store.get(key);
} catch (e) {
console.error('存储读取失败:', e);
return null;
}
},
set(key, value, expires) {
try {
store.set(key, value, expires);
return true;
} catch (e) {
console.error('存储写入失败:', e);
if (e.name === 'QuotaExceededError') {
console.warn('存储空间不足,尝试清理过期数据');
clearExpiredData();
try {
store.set(key, value, expires);
return true;
} catch (e2) {
console.error('清理后仍然存储失败:', e2);
return false;
}
}
return false;
}
}
};
const clearExpiredData = () => {
Object.keys(localStorage).forEach(key => {
try {
const value = JSON.parse(localStorage.getItem(key));
if (value.expires && new Date(value.expires) < new Date()) {
localStorage.removeItem(key);
}
} catch (e) {}
});
};
最佳实践 #
1. 数据序列化 #
javascript
const storeObject = (key, obj, expires) => {
store.set(key, JSON.stringify(obj), expires);
};
const getObject = (key) => {
const str = store.get(key);
return str ? JSON.parse(str) : null;
};
2. 敏感数据加密 #
javascript
import md5 from './md5';
const encryptData = (data, secret) => {
const str = JSON.stringify(data);
const encrypted = btoa(str);
const signature = md5(encrypted + secret);
return { encrypted, signature };
};
const decryptData = (encrypted, signature, secret) => {
const expectedSignature = md5(encrypted + secret);
if (signature !== expectedSignature) {
throw new Error('数据签名验证失败');
}
return JSON.parse(atob(encrypted));
};
3. 存储版本管理 #
javascript
const STORAGE_VERSION = '1.0';
const initWithVersion = () => {
const version = localStorage.getItem('__version__');
if (version !== STORAGE_VERSION) {
localStorage.clear();
localStorage.setItem('__version__', STORAGE_VERSION);
}
};
initWithVersion();
下一步 #
现在你已经掌握了本地存储,接下来学习 文件处理,了解文件操作和 Base64 转换!
最后更新:2026-04-04