前端 LocalStorage 实战:从入门到熟练,一篇就够了
你是不是还在用
localStorage.setItem/getItem一通乱存?存对象忘写
JSON.stringify拿不到?存满 5M 怎么办?这篇文章带你系统掌握 LocalStorage 的全部玩法,附带一个「笔记宝盒」完整项目。
一、LocalStorage 是什么?
localStorage 是浏览器提供的持久化存储 方案。它就像给每个网站分配了一个小抽屉,你把数据放进去,关闭浏览器、重启电脑,数据依然在,除非手动清除或代码删除。
与它对应的还有 sessionStorage(会话存储,关标签页就没了)、cookie(每次请求自动携带,容量小)。
核心特性:
- 容量约 5MB(不同浏览器略有差异)
- 存储 键值对 ,键和值都必须是字符串
- 同源策略:同一个协议、域名、端口才能共享
- 同步 API,会阻塞 JS 执行(但数据量小,影响可忽略)
- 永远不会过期(除非主动清除)
二、基础用法(5 分钟上手)
js
// 1. 存储数据
localStorage.setItem('name', '张三');
localStorage.setItem('age', '25');
// 2. 读取数据
const name = localStorage.getItem('name'); // '张三'
const age = localStorage.getItem('age'); // '25'
// 3. 删除某一条
localStorage.removeItem('age');
// 4. 清空所有
localStorage.clear();
// 5. 获取 key 名(用于遍历)
const key = localStorage.key(0); // 第0个键名
注意:存储数字、布尔、对象都必须转为字符串。
js
// 存储对象
const user = { id: 1, name: '李四' };
localStorage.setItem('user', JSON.stringify(user));
// 读取对象
const raw = localStorage.getItem('user');
const userObj = JSON.parse(raw);
三、实战场景:一个「笔记宝盒」项目
光说没用,我们做一个完整的"笔记宝盒"应用,包含:添加笔记、删除笔记、编辑笔记、搜索笔记,所有数据存在 localStorage 里。
最终效果预览
- 顶部输入框 + 添加按钮
- 笔记列表显示标题、内容、操作按钮(编辑、删除)
- 搜索框实时过滤
- 关闭浏览器再打开,笔记还在
完整代码(复制即用)
html
<!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>
* { box-sizing: border-box; }
body {
background: #f5f7fb;
font-family: system-ui, -apple-system, sans-serif;
display: flex;
justify-content: center;
padding: 2rem;
}
.app {
max-width: 800px;
width: 100%;
background: white;
border-radius: 1.5rem;
box-shadow: 0 8px 20px rgba(0,0,0,0.05);
padding: 1.5rem;
}
h1 { margin-top: 0; font-size: 1.8rem; display: flex; align-items: center; gap: 8px; }
.add-section { display: flex; gap: 12px; margin-bottom: 1.5rem; flex-wrap: wrap; }
.add-section input, .add-section textarea {
padding: 10px;
border: 1px solid #e2e8f0;
border-radius: 12px;
font-size: 0.9rem;
}
.add-section input { flex: 2; }
.add-section textarea { flex: 3; resize: vertical; }
.add-section button {
background: #3b82f6;
color: white;
border: none;
border-radius: 12px;
padding: 0 20px;
cursor: pointer;
font-weight: bold;
}
.search-box {
width: 100%;
padding: 10px;
margin-bottom: 1.5rem;
border: 1px solid #e2e8f0;
border-radius: 40px;
font-size: 0.9rem;
}
.note-card {
background: #fefce8;
border-left: 6px solid #eab308;
border-radius: 12px;
padding: 12px 16px;
margin-bottom: 12px;
display: flex;
justify-content: space-between;
align-items: flex-start;
transition: 0.2s;
}
.note-card:hover { background: #fff9db; }
.note-info h3 { margin: 0 0 6px 0; font-size: 1.1rem; }
.note-info p { margin: 0; color: #334155; font-size: 0.9rem; word-break: break-all; }
.note-actions { display: flex; gap: 8px; }
.note-actions button {
background: none;
border: none;
cursor: pointer;
font-size: 1.2rem;
padding: 4px 8px;
border-radius: 8px;
}
.edit-btn { color: #3b82f6; }
.delete-btn { color: #ef4444; }
.empty { text-align: center; color: #94a3b8; padding: 2rem; }
</style>
</head>
<body>
<div class="app">
<h1>📒 笔记宝盒 <span style="font-size:0.8rem;">LocalStorage 实战</span></h1>
<div class="add-section">
<input type="text" id="titleInput" placeholder="标题">
<textarea id="contentInput" rows="1" placeholder="内容..."></textarea>
<button id="addBtn">+ 添加笔记</button>
</div>
<input type="text" id="searchInput" class="search-box" placeholder="🔍 搜索标题或内容...">
<div id="notesContainer"></div>
</div>
<script>
// ---------- 存储 key ----------
const STORAGE_KEY = 'note_box';
// ---------- 读取笔记 ----------
function loadNotes() {
const raw = localStorage.getItem(STORAGE_KEY);
if (!raw) return [];
try {
return JSON.parse(raw);
} catch (e) {
return [];
}
}
// ---------- 保存笔记 ----------
function saveNotes(notes) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(notes));
}
// ---------- 全局状态 ----------
let notes = loadNotes(); // 笔记数组 [{ id, title, content }]
let searchKeyword = '';
// ---------- 渲染视图 ----------
function render() {
const container = document.getElementById('notesContainer');
const keyword = searchKeyword.trim().toLowerCase();
// 过滤笔记
let filtered = notes;
if (keyword) {
filtered = notes.filter(note =>
note.title.toLowerCase().includes(keyword) ||
note.content.toLowerCase().includes(keyword)
);
}
if (filtered.length === 0) {
container.innerHTML = '<div class="empty">📭 暂无笔记,添加一条吧~</div>';
return;
}
container.innerHTML = filtered.map(note => `
<div class="note-card" data-id="${note.id}">
<div class="note-info">
<h3>${escapeHtml(note.title)}</h3>
<p>${escapeHtml(note.content)}</p>
</div>
<div class="note-actions">
<button class="edit-btn" data-action="edit">✏️</button>
<button class="delete-btn" data-action="delete">🗑️</button>
</div>
</div>
`).join('');
}
// 简单的防 XSS
function escapeHtml(str) {
if (!str) return '';
return str.replace(/[&<>]/g, function(m) {
if (m === '&') return '&';
if (m === '<') return '<';
if (m === '>') return '>';
return m;
});
}
// ---------- 添加笔记 ----------
function addNote() {
const title = document.getElementById('titleInput').value.trim();
const content = document.getElementById('contentInput').value.trim();
if (!title && !content) {
alert('请至少填写标题或内容');
return;
}
const newNote = {
id: Date.now(),
title: title || '无标题',
content: content || '无内容'
};
notes.push(newNote);
saveNotes(notes);
// 清空输入框
document.getElementById('titleInput').value = '';
document.getElementById('contentInput').value = '';
render();
}
// ---------- 删除笔记 ----------
function deleteNote(id) {
notes = notes.filter(note => note.id !== id);
saveNotes(notes);
render();
}
// ---------- 编辑笔记(弹窗修改) ----------
function editNote(id) {
const note = notes.find(n => n.id === id);
if (!note) return;
const newTitle = prompt('修改标题', note.title);
const newContent = prompt('修改内容', note.content);
if (newTitle !== null) note.title = newTitle.trim() || '无标题';
if (newContent !== null) note.content = newContent.trim() || '无内容';
saveNotes(notes);
render();
}
// ---------- 事件委托(处理删除/编辑) ----------
function handleContainerClick(e) {
const target = e.target;
const action = target.dataset.action;
const card = target.closest('.note-card');
if (!card) return;
const id = Number(card.dataset.id);
if (action === 'delete') {
if (confirm('确定删除这条笔记吗?')) deleteNote(id);
} else if (action === 'edit') {
editNote(id);
}
}
// ---------- 搜索监听 ----------
function handleSearchInput(e) {
searchKeyword = e.target.value;
render();
}
// ---------- 初始化 ----------
function init() {
render();
document.getElementById('addBtn').addEventListener('click', addNote);
document.getElementById('searchInput').addEventListener('input', handleSearchInput);
document.getElementById('notesContainer').addEventListener('click', handleContainerClick);
// 允许回车添加(可选)
const inputs = ['titleInput', 'contentInput'];
inputs.forEach(id => {
document.getElementById(id).addEventListener('keypress', (e) => {
if (e.key === 'Enter') addNote();
});
});
}
init();
</script>
</body>
</html>
四、代码中的 LocalStorage 知识点总结
- 存储/读取 :
localStorage.setItem(key, value)/getItem(key) - 存储对象 :必须
JSON.stringify,取出后JSON.parse - 删除某条:不能直接删对象内部的某个字段,而是读取整个数组 → 修改数组 → 重新存回去
- 初始化 :页面加载时从
localStorage读取,没有就给空数组 - 修改数据流 :修改
notes数组 →saveNotes→render,单向数据流
五、进阶技巧与注意事项
1. 存储空间超出 5MB 怎么办?
使用 try...catch 捕获 QuotaExceededError,提示用户清理数据。
js
try {
localStorage.setItem('key', 'large string...');
} catch (e) {
if (e.name === 'QuotaExceededError') {
alert('存储已满,请删除部分数据');
}
}
2. 监听 storage 事件(多标签页实时同步)
当你在一个标签页修改 localStorage,其他同源标签页可以监听到变化,用于跨标签通信。
js
window.addEventListener('storage', (e) => {
console.log('key:', e.key, 'oldValue:', e.oldValue, 'newValue:', e.newValue);
// 刷新页面数据,实现多标签实时同步
});
3. 封装一个带过期时间的存储工具
js
function setWithExpiry(key, value, ttlMs) {
const item = {
value: value,
expiry: Date.now() + ttlMs
};
localStorage.setItem(key, JSON.stringify(item));
}
function getWithExpiry(key) {
const raw = localStorage.getItem(key);
if (!raw) return null;
const item = JSON.parse(raw);
if (Date.now() > item.expiry) {
localStorage.removeItem(key);
return null;
}
return item.value;
}
4. LocalStorage vs sessionStorage vs IndexedDB
| 特性 | localStorage | sessionStorage | IndexedDB |
|---|---|---|---|
| 容量 | ~5MB | ~5MB | 很大(几百MB) |
| 生命周期 | 永久 | 标签页关闭即失 | 永久 |
| API 同步性 | 同步 | 同步 | 异步(复杂) |
| 适用场景 | 用户设置、草稿、小数据 | 表单临时缓存 | 大量结构化数据(如离线日志) |
六、总结
LocalStorage 是前端本地存储的"入门砖",简单但够用。通过"笔记宝盒"这个项目,你学会了:
- 基础 CRUD + 持久化
- 存储对象数组的正确姿势
- 搜索过滤 + 事件委托
- 空间不足、多标签同步等进阶知识
现在你可以:
- 把登录状态、主题设置、用户偏好存在 LocalStorage
- 做一个离线可用的便签本
- 甚至做一个简单的 todo list 或日记本
快去动手改改笔记宝盒,给它加上"标签分类"或者"导出为 JSON"功能吧!遇到问题欢迎评论区交流~