Web存储(Web Storage)是HTML5提供的客户端数据存储机制,包括localStorage和sessionStorage两种方式。与传统的Cookie相比,Web存储提供了更大的存储空间和更简单的API。
东巴文(db-w.cn) 认为:Web存储让前端应用拥有了本地数据持久化能力,是构建现代Web应用的重要基础。
| 特性 | localStorage | sessionStorage |
|---|---|---|
| 生命周期 | 永久存储 | 会话期间(关闭浏览器后清除) |
| 存储大小 | 约5MB | 约5MB |
| 作用域 | 同源窗口共享 | 仅当前窗口 |
| 存储位置 | 客户端 | 客户端 |
| 与服务器通信 | 不自动发送 | 不自动发送 |
| 特性 | Web存储 | Cookie |
|---|---|---|
| 存储大小 | 5MB | 4KB |
| HTTP请求 | 不自动发送 | 自动发送 |
| API | 简单易用 | 较复杂 |
| 过期时间 | 可设置或永久 | 必须设置 |
| 跨域 | 同源策略 | 可设置domain |
东巴文点评:Web存储适合存储大量客户端数据,Cookie适合存储需要发送到服务器的少量数据。
// 存储数据
localStorage.setItem('username', '张三');
localStorage.setItem('age', '25');
// 读取数据
const username = localStorage.getItem('username');
const age = localStorage.getItem('age');
console.log(username); // 张三
console.log(age); // 25
// 删除数据
localStorage.removeItem('username');
// 清除所有数据
localStorage.clear();
// 存储数据
localStorage.username = '张三';
localStorage.age = '25';
// 读取数据
console.log(localStorage.username); // 张三
console.log(localStorage.age); // 25
// 删除数据
delete localStorage.username;
// 存储对象(需要JSON序列化)
const user = {
name: '张三',
age: 25,
email: 'zhangsan@example.com'
};
localStorage.setItem('user', JSON.stringify(user));
// 读取对象(需要JSON反序列化)
const storedUser = JSON.parse(localStorage.getItem('user'));
console.log(storedUser.name); // 张三
东巴文点评:localStorage只能存储字符串,存储对象时必须使用JSON.stringify()序列化,读取时使用JSON.parse()反序列化。
// 存储数组
const colors = ['red', 'green', 'blue'];
localStorage.setItem('colors', JSON.stringify(colors));
// 读取数组
const storedColors = JSON.parse(localStorage.getItem('colors'));
console.log(storedColors); // ['red', 'green', 'blue']
// 添加新元素
storedColors.push('yellow');
localStorage.setItem('colors', JSON.stringify(storedColors));
// 方法1:使用getItem
if (localStorage.getItem('username') !== null) {
console.log('用户名存在');
}
// 方法2:使用hasOwnProperty
if (localStorage.hasOwnProperty('username')) {
console.log('用户名存在');
}
// 方法3:使用in操作符
if ('username' in localStorage) {
console.log('用户名存在');
}
// 存储多个数据
localStorage.setItem('name', '张三');
localStorage.setItem('age', '25');
localStorage.setItem('city', '北京');
// 获取键的数量
console.log(localStorage.length); // 3
// 遍历所有键
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
const value = localStorage.getItem(key);
console.log(`${key}: ${value}`);
}
// 使用Object.keys遍历
Object.keys(localStorage).forEach(key => {
const value = localStorage.getItem(key);
console.log(`${key}: ${value}`);
});
// 存储数据
sessionStorage.setItem('token', 'abc123');
sessionStorage.setItem('loginTime', Date.now().toString());
// 读取数据
const token = sessionStorage.getItem('token');
const loginTime = sessionStorage.getItem('loginTime');
console.log(token); // abc123
console.log(loginTime); // 时间戳
// 删除数据
sessionStorage.removeItem('token');
// 清除所有数据
sessionStorage.clear();
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>sessionStorage示例</title>
</head>
<body>
<h1>会话计数器</h1>
<p>当前会话访问次数:<span id="count">0</span></p>
<script>
// 获取访问次数
let count = parseInt(sessionStorage.getItem('visitCount')) || 0;
// 增加访问次数
count++;
// 存储访问次数
sessionStorage.setItem('visitCount', count.toString());
// 显示访问次数
document.getElementById('count').textContent = count;
</script>
</body>
</html>
东巴文点评:sessionStorage适合存储会话期间的临时数据,如用户登录状态、表单数据等。
当localStorage或sessionStorage的数据发生变化时,会触发storage事件。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>storage事件示例</title>
</head>
<body>
<h1>storage事件监听</h1>
<script>
// 监听storage事件
window.addEventListener('storage', function(e) {
console.log('键:', e.key);
console.log('旧值:', e.oldValue);
console.log('新值:', e.newValue);
console.log('URL:', e.url);
console.log('存储区域:', e.storageArea);
});
</script>
</body>
</html>
| 属性 | 说明 |
|---|---|
key |
发生变化的键 |
oldValue |
旧值 |
newValue |
新值 |
url |
发生变化的页面URL |
storageArea |
存储区域对象 |
东巴文点评:storage事件只在同源的其他窗口中触发,当前窗口不会触发。
function getStorageSize() {
let total = 0;
for (let key in localStorage) {
if (localStorage.hasOwnProperty(key)) {
total += localStorage.getItem(key).length + key.length;
}
}
// 转换为KB
return (total * 2 / 1024).toFixed(2) + ' KB';
}
console.log('已使用存储空间:', getStorageSize());
function testStorageLimit() {
const testKey = 'test';
const testValue = 'a';
let data = '';
try {
// 构建测试数据
for (let i = 0; i < 1024 * 1024; i++) {
data += testValue;
localStorage.setItem(testKey, data);
}
} catch (e) {
console.log('存储空间已满');
localStorage.removeItem(testKey);
}
}
function setLocalStorage(key, value) {
try {
localStorage.setItem(key, value);
return true;
} catch (e) {
if (e.name === 'QuotaExceededError') {
console.error('存储空间已满');
} else {
console.error('存储失败:', e.message);
}
return false;
}
}
// 使用示例
if (setLocalStorage('username', '张三')) {
console.log('存储成功');
} else {
console.log('存储失败');
}
东巴文点评:在使用Web存储时,应该始终进行异常处理,避免因存储空间满或其他错误导致程序崩溃。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>用户偏好设置</title>
<style>
body {
transition: background-color 0.3s, color 0.3s;
}
body.dark {
background-color: #333;
color: #fff;
}
body.light {
background-color: #fff;
color: #333;
}
</style>
</head>
<body>
<h1>用户偏好设置</h1>
<label>
<input type="checkbox" id="darkMode"> 深色模式
</label>
<script>
// 加载用户偏好
function loadPreferences() {
const darkMode = localStorage.getItem('darkMode') === 'true';
document.getElementById('darkMode').checked = darkMode;
document.body.className = darkMode ? 'dark' : 'light';
}
// 保存用户偏好
function savePreferences() {
const darkMode = document.getElementById('darkMode').checked;
localStorage.setItem('darkMode', darkMode.toString());
document.body.className = darkMode ? 'dark' : 'light';
}
// 监听变化
document.getElementById('darkMode').addEventListener('change', savePreferences);
// 页面加载时加载偏好
loadPreferences();
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>表单自动保存</title>
</head>
<body>
<h1>表单自动保存</h1>
<form id="myForm">
<div>
<label>姓名:</label>
<input type="text" id="name" name="name">
</div>
<div>
<label>邮箱:</label>
<input type="email" id="email" name="email">
</div>
<div>
<label>留言:</label>
<textarea id="message" name="message"></textarea>
</div>
<button type="submit">提交</button>
<button type="button" id="clearBtn">清除保存的数据</button>
</form>
<script>
const form = document.getElementById('myForm');
// 加载保存的表单数据
function loadFormData() {
const savedData = localStorage.getItem('formData');
if (savedData) {
const data = JSON.parse(savedData);
document.getElementById('name').value = data.name || '';
document.getElementById('email').value = data.email || '';
document.getElementById('message').value = data.message || '';
}
}
// 保存表单数据
function saveFormData() {
const data = {
name: document.getElementById('name').value,
email: document.getElementById('email').value,
message: document.getElementById('message').value
};
localStorage.setItem('formData', JSON.stringify(data));
}
// 监听表单输入
form.addEventListener('input', saveFormData);
// 提交表单
form.addEventListener('submit', function(e) {
e.preventDefault();
console.log('表单提交成功');
localStorage.removeItem('formData');
form.reset();
});
// 清除保存的数据
document.getElementById('clearBtn').addEventListener('click', function() {
localStorage.removeItem('formData');
form.reset();
});
// 页面加载时加载表单数据
loadFormData();
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>购物车示例</title>
<style>
.cart {
border: 1px solid #ccc;
padding: 20px;
margin: 20px 0;
}
.cart-item {
display: flex;
justify-content: space-between;
padding: 10px 0;
border-bottom: 1px solid #eee;
}
.product {
display: inline-block;
margin: 10px;
padding: 10px;
border: 1px solid #ccc;
}
</style>
</head>
<body>
<h1>购物车示例</h1>
<div>
<div class="product">
<h3>商品A</h3>
<p>价格:¥100</p>
<button onclick="addToCart('商品A', 100)">加入购物车</button>
</div>
<div class="product">
<h3>商品B</h3>
<p>价格:¥200</p>
<button onclick="addToCart('商品B', 200)">加入购物车</button>
</div>
<div class="product">
<h3>商品C</h3>
<p>价格:¥300</p>
<button onclick="addToCart('商品C', 300)">加入购物车</button>
</div>
</div>
<div class="cart">
<h2>购物车</h2>
<div id="cartItems"></div>
<p>总计:<span id="totalPrice">¥0</span></p>
<button onclick="clearCart()">清空购物车</button>
</div>
<script>
// 获取购物车数据
function getCart() {
const cart = localStorage.getItem('cart');
return cart ? JSON.parse(cart) : [];
}
// 保存购物车数据
function saveCart(cart) {
localStorage.setItem('cart', JSON.stringify(cart));
renderCart();
}
// 添加商品到购物车
function addToCart(name, price) {
const cart = getCart();
const existingItem = cart.find(item => item.name === name);
if (existingItem) {
existingItem.quantity++;
} else {
cart.push({ name, price, quantity: 1 });
}
saveCart(cart);
}
// 从购物车移除商品
function removeFromCart(name) {
const cart = getCart();
const index = cart.findIndex(item => item.name === name);
if (index > -1) {
if (cart[index].quantity > 1) {
cart[index].quantity--;
} else {
cart.splice(index, 1);
}
}
saveCart(cart);
}
// 清空购物车
function clearCart() {
localStorage.removeItem('cart');
renderCart();
}
// 渲染购物车
function renderCart() {
const cart = getCart();
const cartItemsEl = document.getElementById('cartItems');
const totalPriceEl = document.getElementById('totalPrice');
let html = '';
let total = 0;
cart.forEach(item => {
const itemTotal = item.price * item.quantity;
total += itemTotal;
html += `
<div class="cart-item">
<span>${item.name}</span>
<span>¥${item.price} x ${item.quantity}</span>
<span>¥${itemTotal}</span>
<button onclick="removeFromCart('${item.name}')">-</button>
</div>
`;
});
cartItemsEl.innerHTML = html;
totalPriceEl.textContent = '¥' + total;
}
// 页面加载时渲染购物车
renderCart();
</script>
</body>
</html>
东巴文点评:Web存储非常适合存储购物车、用户偏好、表单数据等客户端数据。
// 不推荐:存储敏感信息
localStorage.setItem('password', '123456');
localStorage.setItem('creditCard', '1234567890123456');
// 推荐:存储非敏感信息
localStorage.setItem('username', '张三');
localStorage.setItem('theme', 'dark');
// 简单的加密示例(实际应用中应使用更强大的加密算法)
function encrypt(text, key) {
let result = '';
for (let i = 0; i < text.length; i++) {
result += String.fromCharCode(text.charCodeAt(i) ^ key.charCodeAt(i % key.length));
}
return btoa(result);
}
function decrypt(text, key) {
text = atob(text);
let result = '';
for (let i = 0; i < text.length; i++) {
result += String.fromCharCode(text.charCodeAt(i) ^ key.charCodeAt(i % key.length));
}
return result;
}
// 存储加密数据
const sensitiveData = '敏感信息';
const encrypted = encrypt(sensitiveData, 'secret-key');
localStorage.setItem('data', encrypted);
// 读取加密数据
const decrypted = decrypt(localStorage.getItem('data'), 'secret-key');
console.log(decrypted); // 敏感信息
// 对存储的数据进行转义
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// 存储转义后的数据
const userInput = '<script>alert("XSS")</script>';
localStorage.setItem('comment', escapeHtml(userInput));
// 读取时数据是安全的
const comment = localStorage.getItem('comment');
console.log(comment); // <script>alert("XSS")</script>
东巴文点评:Web存储容易受到XSS攻击,不要存储敏感信息,必要时对数据进行加密。
class StorageUtil {
constructor(storage = localStorage) {
this.storage = storage;
}
// 设置数据
set(key, value) {
try {
const data = JSON.stringify(value);
this.storage.setItem(key, data);
return true;
} catch (e) {
console.error('存储失败:', e.message);
return false;
}
}
// 获取数据
get(key, defaultValue = null) {
try {
const data = this.storage.getItem(key);
return data ? JSON.parse(data) : defaultValue;
} catch (e) {
console.error('读取失败:', e.message);
return defaultValue;
}
}
// 删除数据
remove(key) {
this.storage.removeItem(key);
}
// 清空数据
clear() {
this.storage.clear();
}
// 检查键是否存在
has(key) {
return this.storage.getItem(key) !== null;
}
// 获取所有键
keys() {
return Object.keys(this.storage);
}
// 获取存储大小
size() {
let total = 0;
for (let key in this.storage) {
if (this.storage.hasOwnProperty(key)) {
total += this.storage.getItem(key).length + key.length;
}
}
return (total * 2 / 1024).toFixed(2) + ' KB';
}
}
// 使用示例
const storage = new StorageUtil();
// 存储对象
storage.set('user', { name: '张三', age: 25 });
// 读取对象
const user = storage.get('user');
console.log(user); // { name: '张三', age: 25 }
// 检查键是否存在
console.log(storage.has('user')); // true
// 获取存储大小
console.log(storage.size());
// 删除数据
storage.remove('user');
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web存储示例 - 东巴文</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
transition: background-color 0.3s, color 0.3s;
}
body.dark {
background-color: #333;
color: #fff;
}
.section {
margin: 30px 0;
padding: 20px;
border: 1px solid #ddd;
border-radius: 5px;
}
.note-list {
list-style: none;
padding: 0;
}
.note-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
margin: 10px 0;
background: #f5f5f5;
border-radius: 3px;
}
body.dark .note-item {
background: #444;
}
button {
padding: 8px 16px;
margin: 5px;
border: none;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 3px;
cursor: pointer;
}
button:hover {
opacity: 0.9;
}
input, textarea {
padding: 8px;
margin: 5px 0;
border: 1px solid #ddd;
border-radius: 3px;
width: 100%;
box-sizing: border-box;
}
.stats {
display: flex;
gap: 20px;
margin: 20px 0;
}
.stat-item {
flex: 1;
padding: 15px;
background: #f5f5f5;
border-radius: 5px;
text-align: center;
}
body.dark .stat-item {
background: #444;
}
</style>
</head>
<body>
<h1>Web存储示例</h1>
<div class="stats">
<div class="stat-item">
<h3>localStorage使用量</h3>
<p id="localStorageSize">0 KB</p>
</div>
<div class="stat-item">
<h3>sessionStorage使用量</h3>
<p id="sessionStorageSize">0 KB</p>
</div>
</div>
<div class="section">
<h2>用户偏好设置</h2>
<label>
<input type="checkbox" id="darkMode"> 深色模式
</label>
</div>
<div class="section">
<h2>笔记管理</h2>
<input type="text" id="noteInput" placeholder="输入笔记内容">
<button onclick="addNote()">添加笔记</button>
<ul class="note-list" id="noteList"></ul>
<button onclick="clearNotes()">清空所有笔记</button>
</div>
<div class="section">
<h2>会话计数器</h2>
<p>当前会话访问次数:<span id="visitCount">0</span></p>
</div>
<script>
// 工具类
class StorageUtil {
constructor(storage) {
this.storage = storage;
}
set(key, value) {
try {
this.storage.setItem(key, JSON.stringify(value));
return true;
} catch (e) {
console.error('存储失败:', e.message);
return false;
}
}
get(key, defaultValue = null) {
try {
const data = this.storage.getItem(key);
return data ? JSON.parse(data) : defaultValue;
} catch (e) {
console.error('读取失败:', e.message);
return defaultValue;
}
}
remove(key) {
this.storage.removeItem(key);
}
clear() {
this.storage.clear();
}
size() {
let total = 0;
for (let key in this.storage) {
if (this.storage.hasOwnProperty(key)) {
total += this.storage.getItem(key).length + key.length;
}
}
return (total * 2 / 1024).toFixed(2) + ' KB';
}
}
const localStore = new StorageUtil(localStorage);
const sessionStore = new StorageUtil(sessionStorage);
// 更新存储大小显示
function updateStorageSize() {
document.getElementById('localStorageSize').textContent = localStore.size();
document.getElementById('sessionStorageSize').textContent = sessionStore.size();
}
// 深色模式
function loadTheme() {
const darkMode = localStore.get('darkMode', false);
document.getElementById('darkMode').checked = darkMode;
document.body.className = darkMode ? 'dark' : '';
}
function saveTheme() {
const darkMode = document.getElementById('darkMode').checked;
localStore.set('darkMode', darkMode);
document.body.className = darkMode ? 'dark' : '';
updateStorageSize();
}
document.getElementById('darkMode').addEventListener('change', saveTheme);
// 笔记管理
function loadNotes() {
const notes = localStore.get('notes', []);
const noteList = document.getElementById('noteList');
noteList.innerHTML = notes.map((note, index) => `
<li class="note-item">
<span>${note}</span>
<button onclick="deleteNote(${index})">删除</button>
</li>
`).join('');
}
function addNote() {
const input = document.getElementById('noteInput');
const note = input.value.trim();
if (note) {
const notes = localStore.get('notes', []);
notes.push(note);
localStore.set('notes', notes);
input.value = '';
loadNotes();
updateStorageSize();
}
}
function deleteNote(index) {
const notes = localStore.get('notes', []);
notes.splice(index, 1);
localStore.set('notes', notes);
loadNotes();
updateStorageSize();
}
function clearNotes() {
localStore.remove('notes');
loadNotes();
updateStorageSize();
}
// 会话计数器
function updateVisitCount() {
let count = sessionStore.get('visitCount', 0);
count++;
sessionStore.set('visitCount', count);
document.getElementById('visitCount').textContent = count;
updateStorageSize();
}
// 初始化
loadTheme();
loadNotes();
updateVisitCount();
updateStorageSize();
</script>
</body>
</html>
// 推荐:使用前缀分类
localStorage.setItem('user:name', '张三');
localStorage.setItem('user:age', '25');
localStorage.setItem('settings:theme', 'dark');
localStorage.setItem('settings:language', 'zh-CN');
// 不推荐:无分类
localStorage.setItem('name', '张三');
localStorage.setItem('age', '25');
localStorage.setItem('theme', 'dark');
localStorage.setItem('language', 'zh-CN');
function safeSetItem(key, value) {
try {
localStorage.setItem(key, value);
return true;
} catch (e) {
if (e.name === 'QuotaExceededError') {
console.error('存储空间已满,请清理数据');
}
return false;
}
}
// 清理过期数据
function cleanExpiredData() {
const now = Date.now();
const keys = Object.keys(localStorage);
keys.forEach(key => {
if (key.startsWith('temp:')) {
const data = JSON.parse(localStorage.getItem(key));
if (data.expires && data.expires < now) {
localStorage.removeItem(key);
}
}
});
}
// 设置带过期时间的数据
function setWithExpiry(key, value, expiryInMs) {
const item = {
value: value,
expires: Date.now() + expiryInMs
};
localStorage.setItem(key, JSON.stringify(item));
}
// 获取带过期时间的数据
function getWithExpiry(key) {
const itemStr = localStorage.getItem(key);
if (!itemStr) return null;
const item = JSON.parse(itemStr);
if (item.expires && Date.now() > item.expires) {
localStorage.removeItem(key);
return null;
}
return item.value;
}
问题1:localStorage和sessionStorage的主要区别是?
A. 存储大小不同 B. 生命周期不同 C. API不同 D. 存储位置不同
<details> <summary>点击查看答案</summary>答案:B
东巴文解释:localStorage是永久存储,除非手动删除;sessionStorage是会话存储,关闭浏览器后自动清除。
</details>问题2:以下哪个方法用于清除localStorage中的所有数据?
A. localStorage.removeItem()
B. localStorage.deleteAll()
C. localStorage.clear()
D. localStorage.empty()
答案:C
东巴文解释:localStorage.clear()方法用于清除localStorage中的所有数据。
任务:创建一个待办事项列表,使用localStorage存储数据,实现添加、删除、标记完成等功能。
<details> <summary>点击查看参考答案</summary><!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>待办事项列表</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 600px;
margin: 50px auto;
padding: 20px;
}
.todo-item {
display: flex;
align-items: center;
padding: 10px;
margin: 10px 0;
background: #f5f5f5;
border-radius: 5px;
}
.todo-item.completed span {
text-decoration: line-through;
opacity: 0.5;
}
input[type="text"] {
padding: 10px;
font-size: 16px;
border: 1px solid #ddd;
border-radius: 5px;
width: 70%;
}
button {
padding: 10px 20px;
margin-left: 10px;
border: none;
background: #667eea;
color: white;
border-radius: 5px;
cursor: pointer;
}
button:hover {
opacity: 0.9;
}
</style>
</head>
<body>
<h1>待办事项列表</h1>
<div>
<input type="text" id="todoInput" placeholder="输入待办事项">
<button onclick="addTodo()">添加</button>
</div>
<div id="todoList"></div>
<script>
let todos = JSON.parse(localStorage.getItem('todos')) || [];
function saveTodos() {
localStorage.setItem('todos', JSON.stringify(todos));
}
function renderTodos() {
const list = document.getElementById('todoList');
list.innerHTML = todos.map((todo, index) => `
<div class="todo-item ${todo.completed ? 'completed' : ''}">
<input type="checkbox" ${todo.completed ? 'checked' : ''}
onchange="toggleTodo(${index})">
<span style="flex: 1; margin: 0 10px;">${todo.text}</span>
<button onclick="deleteTodo(${index})">删除</button>
</div>
`).join('');
}
function addTodo() {
const input = document.getElementById('todoInput');
const text = input.value.trim();
if (text) {
todos.push({ text, completed: false });
saveTodos();
renderTodos();
input.value = '';
}
}
function toggleTodo(index) {
todos[index].completed = !todos[index].completed;
saveTodos();
renderTodos();
}
function deleteTodo(index) {
todos.splice(index, 1);
saveTodos();
renderTodos();
}
renderTodos();
</script>
</body>
</html>
</details>
东巴文(db-w.cn) - 让编程学习更有趣、更高效!