pin_drop当前位置:知识文库 ❯ 图文
HTML5 API:HTML5 LocalStorage - 完整教程与代码示例
一、教程简介
LocalStorage 是 Web Storage API 中的持久化存储方案,提供了一种在浏览器端长期存储键值对数据的机制。与 sessionStorage 的会话级存储不同,localStorage 中存储的数据没有过期时间,除非被显式删除,否则将永久保存在浏览器中。localStorage 的同源共享特性使其成为存储用户偏好设置、主题配置、离线数据缓存等持久化数据的首选方案。
二、核心概念
持久化特性
代码示例
写入 localStorage → 数据永久保存
│
├── 页面刷新 → 数据保留
├── 标签页关闭 → 数据保留
├── 浏览器重启 → 数据保留
├── 同源页面 → 数据共享
│
└── 手动清除 → 数据删除
├── localStorage.removeItem(key)
├── localStorage.clear()
├── 用户清除浏览器数据
└── 浏览器存储空间不足时自动清理同源共享
localStorage 遵循同源策略,同一源下的所有页面共享同一份 localStorage 数据:
代码示例
https://example.com/page1 ──┐
https://example.com/page2 ──┤── 共享同一份 localStorage
https://example.com:443/a ──┘ (可互相读写)
http://example.com ────── 不同协议,独立存储
https://api.example.com ────── 不同域名,独立存储
https://example.com:8080 ────── 不同端口,独立存储存储容量
当存储空间满时,继续写入会抛出 QuotaExceededError 异常。
API 接口
代码示例
localStorage.setItem(key, value); // 设置数据
localStorage.getItem(key); // 读取数据
localStorage.removeItem(key); // 删除指定键
localStorage.clear(); // 清空所有数据
localStorage.key(index); // 按索引获取键名
localStorage.length; // 获取存储条目数量三、语法与用法
基本操作
代码示例
// 存储
localStorage.setItem('username', '张三');
localStorage.setItem('theme', 'dark');
// 读取
const username = localStorage.getItem('username'); // '张三'
const theme = localStorage.getItem('theme'); // 'dark'
// 删除
localStorage.removeItem('username');
// 清空
localStorage.clear();
// 遍历
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
console.log(key, localStorage.getItem(key));
}存储复杂数据类型
代码示例
// 对象
const settings = { theme: 'dark', fontSize: 16, language: 'zh-CN' };
localStorage.setItem('settings', JSON.stringify(settings));
const saved = JSON.parse(localStorage.getItem('settings'));
// 数组
const history = ['首页', '关于', '联系'];
localStorage.setItem('history', JSON.stringify(history));
const savedHistory = JSON.parse(localStorage.getItem('history'));
// 数字(注意类型转换)
localStorage.setItem('count', '42');
const count = parseInt(localStorage.getItem('count'), 10); // 42
// 布尔值
localStorage.setItem('agreed', 'true');
const agreed = localStorage.getItem('agreed') === 'true'; // true
// 日期
const now = new Date().toISOString();
localStorage.setItem('lastVisit', now);
const lastVisit = new Date(localStorage.getItem('lastVisit'));Storage 事件
当同源的其他页面修改 localStorage 时触发:
代码示例
window.addEventListener('storage', function(e) {
console.log('键:', e.key); // 被修改的键
console.log('旧值:', e.oldValue); // 修改前的值
console.log('新值:', e.newValue); // 修改后的值
console.log('URL:', e.url); // 触发变化的页面URL
console.log('存储区:', e.storageArea); // localStorage 引用
});注意:
storage事件只在其他同源页面中触发,当前页面自身的修改不会触发。
四、代码示例
示例一:主题切换与偏好设置持久化
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LocalStorage - 主题与偏好设置</title>
<style>
:root {
--bg-primary: #ffffff;
--bg-secondary: #f5f7fa;
--bg-card: #ffffff;
--text-primary: #2d3436;
--text-secondary: #636e72;
--border-color: #e0e0e0;
--accent: #0984e3;
--shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
}
[data-theme="dark"] {
--bg-primary: #1a1a2e;
--bg-secondary: #16213e;
--bg-card: #0f3460;
--text-primary: #e0e0e0;
--text-secondary: #7a8ba0;
--border-color: #1a3a5c;
--accent: #00cec9;
--shadow: 0 2px 12px rgba(0, 0, 0, 0.3);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: var(--bg-secondary);
color: var(--text-primary);
min-height: 100vh;
padding: 30px 20px;
transition: background 0.3s, color 0.3s;
}
.container { max-width: 640px; margin: 0 auto; }
h1 { font-size: 24px; margin-bottom: 8px; }
.desc { color: var(--text-secondary); font-size: 14px; margin-bottom: 24px; }
.card {
background: var(--bg-card);
border-radius: 12px;
padding: 24px;
margin-bottom: 16px;
box-shadow: var(--shadow);
border: 1px solid var(--border-color);
transition: background 0.3s, border-color 0.3s;
}
.card h2 { font-size: 16px; margin-bottom: 16px; }
.setting-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 14px 0;
border-bottom: 1px solid var(--border-color);
}
.setting-row:last-child { border-bottom: none; }
.setting-label { font-size: 14px; }
.setting-desc { font-size: 12px; color: var(--text-secondary); margin-top: 2px; }
/* 开关 */
.toggle {
position: relative;
width: 48px;
height: 26px;
background: #ccc;
border-radius: 13px;
cursor: pointer;
transition: background 0.3s;
}
.toggle.active { background: var(--accent); }
.toggle::after {
content: '';
position: absolute;
top: 3px;
left: 3px;
width: 20px;
height: 20px;
background: #fff;
border-radius: 50%;
transition: transform 0.3s;
}
.toggle.active::after { transform: translateX(22px); }
/* 滑块 */
.slider-group { display: flex; align-items: center; gap: 12px; }
input[type="range"] {
width: 120px;
accent-color: var(--accent);
}
.slider-value {
font-size: 14px;
font-weight: 600;
min-width: 36px;
text-align: center;
}
/* 选择器 */
.select-group { display: flex; gap: 8px; }
.select-btn {
padding: 6px 16px;
border: 1px solid var(--border-color);
border-radius: 6px;
background: var(--bg-secondary);
color: var(--text-primary);
font-size: 13px;
cursor: pointer;
transition: all 0.2s;
}
.select-btn.active {
background: var(--accent);
color: #fff;
border-color: var(--accent);
}
.preview-text {
margin-top: 16px;
padding: 16px;
background: var(--bg-secondary);
border-radius: 8px;
line-height: 1.8;
transition: font-size 0.3s;
}
</style>
</head>
<body>
<div class="container">
<h1>偏好设置</h1>
<p class="desc">所有设置自动保存到 localStorage,刷新页面后保持不变。</p>
<div class="card">
<h2>外观</h2>
<div class="setting-row">
<div>
<div class="setting-label">深色模式</div>
<div class="setting-desc">切换页面主题</div>
</div>
<div class="toggle" id="themeToggle" onclick="toggleTheme()"></div>
</div>
<div class="setting-row">
<div>
<div class="setting-label">字体大小</div>
<div class="setting-desc">调整正文字体大小</div>
</div>
<div class="slider-group">
<input type="range" id="fontSizeSlider" min="12" max="24" value="16" oninput="changeFontSize(this.value)">
<span class="slider-value" id="fontSizeValue">16px</span>
</div>
</div>
<div class="setting-row">
<div>
<div class="setting-label">语言</div>
<div class="setting-desc">选择界面语言</div>
</div>
<div class="select-group">
<button class="select-btn active" data-lang="zh-CN" onclick="changeLang('zh-CN')">中文</button>
<button class="select-btn" data-lang="en-US" onclick="changeLang('en-US')">English</button>
</div>
</div>
</div>
<div class="card">
<h2>通知</h2>
<div class="setting-row">
<div>
<div class="setting-label">推送通知</div>
<div class="setting-desc">接收新消息推送</div>
</div>
<div class="toggle" id="notifToggle" onclick="toggleNotif()"></div>
</div>
<div class="setting-row">
<div>
<div class="setting-label">邮件通知</div>
<div class="setting-desc">通过邮件接收更新</div>
</div>
<div class="toggle" id="emailToggle" onclick="toggleEmail()"></div>
</div>
</div>
<div class="card">
<h2>预览效果</h2>
<div class="preview-text" id="previewText" style="font-size:16px;">
这是一段预览文字,字体大小会随设置变化。所有偏好设置会自动保存到 localStorage 中,即使关闭浏览器后再次打开也会保持之前的设置。
</div>
</div>
</div>
<script>
const PREFS_KEY = 'userPreferences';
// 默认偏好
const defaultPrefs = {
theme: 'light',
fontSize: 16,
language: 'zh-CN',
notifications: false,
emailNotif: false
};
// 加载偏好
function loadPreferences() {
try {
const saved = localStorage.getItem(PREFS_KEY);
return saved ? { ...defaultPrefs, ...JSON.parse(saved) } : { ...defaultPrefs };
} catch (e) {
return { ...defaultPrefs };
}
}
// 保存偏好
function savePreferences(prefs) {
try {
localStorage.setItem(PREFS_KEY, JSON.stringify(prefs));
} catch (e) {
console.error('保存偏好失败:', e);
}
}
// 应用偏好
function applyPreferences(prefs) {
// 主题
document.documentElement.setAttribute('data-theme', prefs.theme);
const themeToggle = document.getElementById('themeToggle');
themeToggle.classList.toggle('active', prefs.theme === 'dark');
// 字体大小
document.getElementById('fontSizeSlider').value = prefs.fontSize;
document.getElementById('fontSizeValue').textContent = prefs.fontSize + 'px';
document.getElementById('previewText').style.fontSize = prefs.fontSize + 'px';
// 语言
document.querySelectorAll('.select-btn[data-lang]').forEach(btn => {
btn.classList.toggle('active', btn.dataset.lang === prefs.language);
});
// 通知
document.getElementById('notifToggle').classList.toggle('active', prefs.notifications);
document.getElementById('emailToggle').classList.toggle('active', prefs.emailNotif);
}
function toggleTheme() {
const prefs = loadPreferences();
prefs.theme = prefs.theme === 'dark' ? 'light' : 'dark';
savePreferences(prefs);
applyPreferences(prefs);
}
function changeFontSize(size) {
const prefs = loadPreferences();
prefs.fontSize = parseInt(size);
savePreferences(prefs);
applyPreferences(prefs);
}
function changeLang(lang) {
const prefs = loadPreferences();
prefs.language = lang;
savePreferences(prefs);
applyPreferences(prefs);
}
function toggleNotif() {
const prefs = loadPreferences();
prefs.notifications = !prefs.notifications;
savePreferences(prefs);
applyPreferences(prefs);
}
function toggleEmail() {
const prefs = loadPreferences();
prefs.emailNotif = !prefs.emailNotif;
savePreferences(prefs);
applyPreferences(prefs);
}
// 初始化
const currentPrefs = loadPreferences();
applyPreferences(currentPrefs);
</script>
</body>
</html>示例二:带过期时间和 LRU 淘汰的缓存管理
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LocalStorage - 缓存管理</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: #1e272e;
min-height: 100vh;
padding: 30px 20px;
color: #d2dae2;
}
.container { max-width: 700px; margin: 0 auto; }
h1 { font-size: 24px; color: #fff; margin-bottom: 8px; }
.desc { color: #808e9b; font-size: 14px; margin-bottom: 24px; }
.card {
background: #2d3436;
border-radius: 12px;
padding: 24px;
margin-bottom: 16px;
border: 1px solid #485460;
}
.card h2 { font-size: 16px; color: #fff; margin-bottom: 16px; }
.input-row { display: flex; gap: 10px; margin-bottom: 12px; }
input, select {
padding: 8px 12px;
border: 1px solid #485460;
border-radius: 6px;
background: #1e272e;
color: #d2dae2;
font-size: 14px;
outline: none;
}
input:focus, select:focus { border-color: #0fbcf9; }
input { flex: 1; }
button {
padding: 8px 18px;
border: none;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.btn-blue { background: #0fbcf9; color: #1e272e; }
.btn-blue:hover { background: #0da8d6; }
.btn-green { background: #05c46b; color: #fff; }
.btn-green:hover { background: #04a85d; }
.btn-red { background: #ff3f34; color: #fff; }
.btn-red:hover { background: #e0352b; }
.btn-yellow { background: #ffc048; color: #1e272e; }
.btn-yellow:hover { background: #e5ab3e; }
.cache-table { width: 100%; border-collapse: collapse; margin-top: 12px; }
.cache-table th, .cache-table td {
padding: 10px 8px;
text-align: left;
border-bottom: 1px solid #485460;
font-size: 13px;
}
.cache-table th { color: #808e9b; font-weight: 500; }
.cache-table td { color: #d2dae2; }
.expired { color: #ff3f34 !important; }
.stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
margin-bottom: 16px;
}
.stat {
background: #1e272e;
border-radius: 8px;
padding: 14px;
text-align: center;
}
.stat-label { font-size: 11px; color: #808e9b; margin-bottom: 4px; }
.stat-value { font-size: 20px; font-weight: 700; color: #0fbcf9; }
.btn-group { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
.empty-msg { text-align: center; color: #808e9b; padding: 20px; font-size: 13px; }
</style>
</head>
<body>
<div class="container">
<h1>LocalStorage 缓存管理</h1>
<p class="desc">支持过期时间和 LRU 淘汰策略的 localStorage 缓存系统。</p>
<div class="stats">
<div class="stat">
<div class="stat-label">缓存条目</div>
<div class="stat-value" id="statCount">0</div>
</div>
<div class="stat">
<div class="stat-label">已用空间</div>
<div class="stat-value" id="statSize">0 KB</div>
</div>
<div class="stat">
<div class="stat-label">过期条目</div>
<div class="stat-value" id="statExpired">0</div>
</div>
</div>
<div class="card">
<h2>添加缓存</h2>
<div class="input-row">
<input type="text" id="cacheKey" placeholder="键名">
<input type="text" id="cacheValue" placeholder="值">
<select id="cacheTTL">
<option value="60000">1 分钟</option>
<option value="300000">5 分钟</option>
<option value="3600000" selected>1 小时</option>
<option value="86400000">1 天</option>
<option value="0">永不过期</option>
</select>
</div>
<div class="btn-group">
<button class="btn-blue" onclick="addCache()">添加</button>
<button class="btn-yellow" onclick="addDemoData()">添加示例数据</button>
<button class="btn-red" onclick="clearExpired()">清除过期</button>
<button class="btn-red" onclick="clearAll()">清空所有</button>
</div>
</div>
<div class="card">
<h2>缓存列表</h2>
<div id="cacheList"></div>
</div>
</div>
<script>
const CACHE_PREFIX = 'lru_cache_';
const MAX_ENTRIES = 20;
function getCache(key) {
const raw = localStorage.getItem(CACHE_PREFIX + key);
if (!raw) return null;
try {
const entry = JSON.parse(raw);
if (entry.expire && Date.now() > entry.expire) {
localStorage.removeItem(CACHE_PREFIX + key);
return null;
}
// 更新访问时间(LRU)
entry.lastAccess = Date.now();
localStorage.setItem(CACHE_PREFIX + key, JSON.stringify(entry));
return entry.value;
} catch (e) {
return null;
}
}
function setCache(key, value, ttlMs) {
// 检查是否需要淘汰
evictIfNeeded();
const entry = {
value: value,
created: Date.now(),
lastAccess: Date.now(),
expire: ttlMs > 0 ? Date.now() + ttlMs : null
};
try {
localStorage.setItem(CACHE_PREFIX + key, JSON.stringify(entry));
return true;
} catch (e) {
// 空间不足,执行 LRU 淘汰后重试
lruEvict();
try {
localStorage.setItem(CACHE_PREFIX + key, JSON.stringify(entry));
return true;
} catch (e2) {
return false;
}
}
}
function removeCache(key) {
localStorage.removeItem(CACHE_PREFIX + key);
}
function getAllCacheEntries() {
const entries = [];
for (let i = 0; i < localStorage.length; i++) {
const k = localStorage.key(i);
if (k.startsWith(CACHE_PREFIX)) {
try {
const entry = JSON.parse(localStorage.getItem(k));
const key = k.slice(CACHE_PREFIX.length);
entries.push({ key: key, ...entry });
} catch (e) { /* 忽略 */ }
}
}
return entries.sort((a, b) => b.lastAccess - a.lastAccess);
}
function evictIfNeeded() {
const entries = getAllCacheEntries();
if (entries.length >= MAX_ENTRIES) {
lruEvict();
}
}
function lruEvict() {
const entries = getAllCacheEntries();
if (entries.length === 0) return;
// 淘汰最久未访问的条目
const lru = entries[entries.length - 1];
removeCache(lru.key);
}
function clearExpired() {
const entries = getAllCacheEntries();
let count = 0;
entries.forEach(entry => {
if (entry.expire && Date.now() > entry.expire) {
removeCache(entry.key);
count++;
}
});
refreshUI();
alert('已清除 ' + count + ' 条过期数据');
}
function clearAll() {
const entries = getAllCacheEntries();
entries.forEach(entry => removeCache(entry.key));
refreshUI();
}
function addCache() {
const key = document.getElementById('cacheKey').value.trim();
const value = document.getElementById('cacheValue').value.trim();
const ttl = parseInt(document.getElementById('cacheTTL').value);
if (!key) { alert('请输入键名'); return; }
setCache(key, value, ttl);
document.getElementById('cacheKey').value = '';
document.getElementById('cacheValue').value = '';
refreshUI();
}
function addDemoData() {
const demos = [
{ key: 'api_users', value: JSON.stringify([{ id: 1, name: '张三' }, { id: 2, name: '李四' }]), ttl: 3600000 },
{ key: 'api_posts', value: JSON.stringify([{ id: 1, title: '文章一' }, { id: 2, title: '文章二' }]), ttl: 300000 },
{ key: 'config', value: JSON.stringify({ theme: 'dark', lang: 'zh' }), ttl: 0 },
{ key: 'temp_token', value: 'eyJhbGciOiJIUzI1NiJ9.demo', ttl: 60000 },
{ key: 'search_history', value: JSON.stringify(['HTML5', 'CSS3', 'JavaScript']), ttl: 86400000 },
];
demos.forEach(d => setCache(d.key, d.value, d.ttl));
refreshUI();
}
function refreshUI() {
const entries = getAllCacheEntries();
const now = Date.now();
// 统计
document.getElementById('statCount').textContent = entries.length;
let totalSize = 0;
let expiredCount = 0;
entries.forEach(entry => {
const raw = localStorage.getItem(CACHE_PREFIX + entry.key);
totalSize += raw ? raw.length : 0;
if (entry.expire && now > entry.expire) expiredCount++;
});
document.getElementById('statSize').textContent = (totalSize * 2 / 1024).toFixed(2) + ' KB';
document.getElementById('statExpired').textContent = expiredCount;
// 列表
const listEl = document.getElementById('cacheList');
if (entries.length === 0) {
listEl.innerHTML = '<div class="empty-msg">暂无缓存数据</div>';
return;
}
let html = '<table class="cache-table"><thead><tr><th>键名</th><th>值</th><th>过期时间</th><th>最后访问</th><th>操作</th></tr></thead><tbody>';
entries.forEach(entry => {
const isExpired = entry.expire && now > entry.expire;
const expireText = entry.expire
? new Date(entry.expire).toLocaleTimeString('zh-CN')
: '永不过期';
const accessText = new Date(entry.lastAccess).toLocaleTimeString('zh-CN');
const valueDisplay = entry.value.length > 30 ? entry.value.substring(0, 30) + '...' : entry.value;
html += '<tr class="' + (isExpired ? 'expired' : '') + '">' +
'<td>' + entry.key + '</td>' +
'<td style="font-family:monospace;font-size:12px;">' + valueDisplay + '</td>' +
'<td>' + expireText + (isExpired ? ' (已过期)' : '') + '</td>' +
'<td>' + accessText + '</td>' +
'<td><button class="btn-red" style="padding:4px 10px;font-size:11px;" onclick="removeCache(\'' + entry.key + '\');refreshUI();">删除</button></td>' +
'</tr>';
});
html += '</tbody></table>';
listEl.innerHTML = html;
}
refreshUI();
</script>
</body>
</html>五、浏览器兼容性
六、注意事项与最佳实践
1. 存储容量管理
代码示例
// 检测剩余可用空间(间接方法)
function getLocalStorageSize() {
let total = 0;
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
const value = localStorage.getItem(key);
total += key.length + value.length;
}
// UTF-16 编码,每字符 2 字节
return (total * 2 / 1024 / 1024).toFixed(2) + ' MB';
}
// 安全写入,处理空间不足
function safeSetItem(key, value) {
try {
localStorage.setItem(key, value);
return true;
} catch (e) {
if (e.name === 'QuotaExceededError' ||
e.code === 22 ||
e.code === 1014) {
console.error('localStorage 空间不足');
// 执行清理策略
}
return false;
}
}2. 数据安全
-
不要存储敏感数据:密码、令牌等不应存储在 localStorage
-
防范 XSS:存储的数据如果被恶意脚本读取,可能导致信息泄露
-
数据验证:读取后应验证数据完整性
代码示例
// 读取时验证数据
function getValidatedData(key, schema) {
const raw = localStorage.getItem(key);
if (!raw) return null;
try {
const data = JSON.parse(raw);
// 简单验证
if (schema.required) {
for (const field of schema.required) {
if (data[field] === undefined) {
localStorage.removeItem(key);
return null;
}
}
}
return data;
} catch (e) {
localStorage.removeItem(key);
return null;
}
}3. 命名空间与版本管理
代码示例
// 使用前缀避免键名冲突
const APP_PREFIX = 'myapp_v1_';
function prefixedKey(key) {
return APP_PREFIX + key;
}
// 版本迁移
function migrateStorage(oldVersion, newVersion) {
if (oldVersion !== newVersion) {
const oldPrefix = 'myapp_v' + oldVersion + '_';
const newPrefix = 'myapp_v' + newVersion + '_';
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key.startsWith(oldPrefix)) {
const newKey = key.replace(oldPrefix, newPrefix);
localStorage.setItem(newKey, localStorage.getItem(key));
localStorage.removeItem(key);
}
}
}
}4. 性能优化
-
避免频繁读写:使用内存变量缓存
-
大数据使用 IndexedDB:替代 localStorage 存储大量数据
-
JSON 序列化开销:注意数据大小对性能的影响
七、代码规范示例
代码示例
// 推荐:完整的 localStorage 管理类
class LocalStorageManager {
constructor(options = {}) {
this.prefix = options.prefix || '';
this.version = options.version || '1';
this.maxAge = options.maxAge || null;
this._cache = new Map();
}
_getKey(key) {
return this.prefix + '_v' + this.version + '_' + key;
}
set(key, value, maxAge = null) {
const entry = {
value: value,
timestamp: Date.now(),
maxAge: maxAge || this.maxAge
};
const serialized = JSON.stringify(entry);
try {
localStorage.setItem(this._getKey(key), serialized);
this._cache.set(key, entry);
return true;
} catch (e) {
console.error('localStorage 写入失败:', e);
return false;
}
}
get(key, defaultValue = null) {
// 先查内存缓存
if (this._cache.has(key)) {
const cached = this._cache.get(key);
if (!this._isExpired(cached)) return cached.value;
this._cache.delete(key);
}
const raw = localStorage.getItem(this._getKey(key));
if (!raw) return defaultValue;
try {
const entry = JSON.parse(raw);
if (this._isExpired(entry)) {
this.remove(key);
return defaultValue;
}
this._cache.set(key, entry);
return entry.value;
} catch (e) {
this.remove(key);
return defaultValue;
}
}
remove(key) {
localStorage.removeItem(this._getKey(key));
this._cache.delete(key);
}
has(key) {
return this.get(key) !== null;
}
clear() {
const keys = [];
for (let i = 0; i < localStorage.length; i++) {
const k = localStorage.key(i);
if (k.startsWith(this.prefix + '_v' + this.version + '_')) {
keys.push(k);
}
}
keys.forEach(k => localStorage.removeItem(k));
this._cache.clear();
}
_isExpired(entry) {
if (!entry.maxAge) return false;
return Date.now() - entry.timestamp > entry.maxAge;
}
getSize() {
let total = 0;
for (let i = 0; i < localStorage.length; i++) {
const k = localStorage.key(i);
if (k.startsWith(this.prefix + '_v' + this.version + '_')) {
total += k.length + localStorage.getItem(k).length;
}
}
return (total * 2 / 1024).toFixed(2) + ' KB';
}
}
// 使用示例
const store = new LocalStorageManager({ prefix: 'myapp', version: '2' });
store.set('user', { name: '张三' });
store.set('token', 'abc123', 3600000); // 1小时过期
console.log(store.get('user'));
console.log(store.getSize());八、常见问题与解决方案
Q1:localStorage 数据突然丢失?
可能原因:
-
用户手动清除了浏览器数据
-
浏览器存储空间不足时自动清理
-
隐私模式下数据在关闭后清除
-
浏览器升级或重装
解决方案:重要数据应同时存储在服务端,localStorage 仅作为缓存使用。
Q2:如何实现 localStorage 的数据加密?
代码示例
// 简单加密(非安全级别,仅增加读取难度)
function encrypt(data, key) {
return btoa(encodeURIComponent(data).split('').reverse().join(''));
}
function decrypt(data, key) {
return decodeURIComponent(atob(data).split('').reverse().join(''));
}
// 使用
localStorage.setItem('secret', encrypt(JSON.stringify(data)));
const decrypted = JSON.parse(decrypt(localStorage.getItem('secret')));Q3:多个模块使用 localStorage 时键名冲突?
解决方案:使用命名空间前缀,如 module_name_key,或使用封装类自动添加前缀。
Q4:如何监听同一页面内的 localStorage 变化?
原因:storage 事件只在其他标签页触发,当前页面不触发。
解决方案:重写 localStorage 方法或使用自定义事件。
代码示例
// 重写 setItem 触发自定义事件
const originalSetItem = localStorage.setItem.bind(localStorage);
localStorage.setItem = function(key, value) {
originalSetItem(key, value);
window.dispatchEvent(new CustomEvent('localstorage-changed', {
detail: { key: key, value: value }
}));
};
// 监听
window.addEventListener('localstorage-changed', function(e) {
console.log('变化:', e.detail.key, e.detail.value);
});九、总结
LocalStorage 提供了浏览器端持久化的键值对存储能力,其同源共享、永久存储的特性使其非常适合存储用户偏好设置、主题配置、离线缓存等数据。使用时需注意存储容量限制(5-10MB)、数据安全(防范 XSS、不存敏感信息)、命名空间管理(避免键名冲突)和异常处理(空间不足、JSON 解析失败)。通过封装带过期时间、LRU 淘汰、内存缓存的工具类,可以让 localStorage 在实际项目中更加安全、高效地使用。
常见问题
localStorage 数据为什么会突然丢失?
可能原因包括:用户手动清除了浏览器数据、浏览器存储空间不足时自动清理、隐私模式下数据在关闭后清除、浏览器升级或重装。解决方案是重要数据应同时存储在服务端,localStorage 仅作为缓存使用。
如何实现 localStorage 的数据加密?
可以使用简单的编码方式增加读取难度,如将数据转换为 Base64 编码后存储。但需要注意这不是真正的加密,只是增加读取门槛。对于敏感数据,建议不要存储在 localStorage 中。
多个模块使用 localStorage 时如何避免键名冲突?
使用命名空间前缀是最佳实践,如 module_name_key 格式,或使用封装类自动添加前缀。这样可以确保不同模块的数据互不干扰。
如何监听同一页面内的 localStorage 变化?
storage 事件只在其他同源标签页中触发,当前页面自身的修改不会触发。解决方案是重写 localStorage 方法(如 setItem)或使用自定义事件来监听变化。
localStorage 的存储容量限制是多少?
不同浏览器的容量不同,Chrome、Firefox、Edge 约为 10MB,Safari 约为 5MB,IE 约为 5-10MB。当存储空间满时,继续写入会抛出 QuotaExceededError 异常,需要进行异常处理和数据清理。
本文涉及AI创作
内容由AI创作,请仔细甄别