LocalStorage và SessionStorage - Hướng Dẫn Toàn Diện Về Lưu Trữ Dữ Liệu Trên Browser
Tìm hiểu cách sử dụng LocalStorage và SessionStorage để lưu trữ dữ liệu trên trình duyệt, so sánh hai API, best practices, giới hạn dung lượng và các mẫu code thực tế.
Trong thế giới phát triển web hiện đại, việc lưu trữ dữ liệu phía client là một phần không thể thiếu. Từ việc ghi nhớ sở thích người dùng, lưu tạm form input, đến đồng bộ trạng thái ứng dụng giữa các tab — LocalStorage và SessionStorage chính là hai công cụ mạnh mẽ nhất mà JavaScript cung cấp cho mục đích này.
Trong bài viết này, bạn sẽ nắm vững cách hoạt động của cả hai API, biết khi nào nên dùng cái nào, cách xử lý dữ liệu phức tạp, và những best practices quan trọng để tránh pitfalls phổ biến.
LocalStorage và SessionStorage là gì?
Cả LocalStorage và SessionStorage đều là một phần của Web Storage API — một tiêu chuẩn W3C được hỗ trợ bởi mọi trình duyệt hiện đại. Chúng cho phép bạn lưu trữ key-value pairs trực tiếp trên trình duyệt của người dùng, với ưu điểm vượt trội so với Cookie:
- Dung lượng lớn hơn: Khoảng 5-10MB (so với ~4KB của Cookie)
- Không gửi lên server: Dữ liệu chỉ tồn tại ở client, không chiếm bandwidth
- API đơn giản: Chỉ cần
setItem(),getItem(),removeItem()— không cần parse header
Sự khác biệt cốt lõi nằm ở thời gian sống của dữ liệu:
| Tính năng | LocalStorage | SessionStorage |
|---|---|---|
| Thời gian sống | Vĩnh viễn (cho đến khi xóa thủ công) | Theo phiên (tab/window) |
| Tự động xóa khi đóng trình duyệt | ❌ Không | ✅ Có |
| Chia sẻ giữa các tab | ✅ Có | ❌ Không |
| Dung lượng | ~5-10MB | ~5-10MB |
| Scope | Domain (cùng origin) | Tab/window cụ thể |
Cú pháp cơ bản
LocalStorage
// Lưu dữ liệu
localStorage.setItem('username', 'devs2');
// Đọc dữ liệu
const username = localStorage.getItem('username');
console.log(username); // 'devs2'
// Xóa một item
localStorage.removeItem('username');
// Xóa tất cả
localStorage.clear();
// Đếm số item
console.log(localStorage.length);
// Lấy item theo index (vì không có keys())
console.log(localStorage.key(0));
SessionStorage
// Lưu dữ liệu phiên
sessionStorage.setItem('cart', JSON.stringify([{ id: 1, qty: 2 }]));
// Đọc dữ liệu phiên
const cart = JSON.parse(sessionStorage.getItem('cart'));
// Xóa item
sessionStorage.removeItem('cart');
// Xóa tất cả data của session
sessionStorage.clear();
Lưu ý: Cú pháp của cả hai API hoàn toàn giống nhau. Sự khác biệt duy nhất là đối tượng (localStorage vs sessionStorage) và thời gian sống của dữ liệu.
Làm việc với Object và Array
Vì Web Storage API chỉ chấp nhận string, bạn cần serialize/deserialize khi làm việc với cấu trúc dữ liệu phức tạp:
// ✅ Lưu object — serialize thành JSON string
const user = { name: 'Nguyễn Văn A', age: 25, role: 'developer' };
localStorage.setItem('user', JSON.stringify(user));
// ✅ Đọc object — parse từ JSON string
const savedUser = JSON.parse(localStorage.getItem('user'));
console.log(savedUser.name); // 'Nguyễn Văn A'
// ✅ Lưu array
const todos = [
{ id: 1, text: 'Học JavaScript', done: false },
{ id: 2, text: 'Xây dựng dự án', done: true }
];
localStorage.setItem('todos', JSON.stringify(todos));
// ✅ Đọc array
const savedTodos = JSON.parse(localStorage.getItem('todos'));
Wrapper class tiện lợi
Để tránh lặp lại JSON.stringify / JSON.parse, tạo một wrapper helper:
class StorageHelper {
static set(key, value) {
try {
localStorage.setItem(key, JSON.stringify(value));
return true;
} catch (e) {
console.error(`[StorageHelper] Lỗi lưu ${key}:`, e);
return false;
}
}
static get(key, defaultValue = null) {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : defaultValue;
} catch (e) {
console.error(`[StorageHelper] Lỗi đọc ${key}:`, e);
return defaultValue;
}
}
static remove(key) {
localStorage.removeItem(key);
}
static clear() {
localStorage.clear();
}
}
// Sử dụng
StorageHelper.set('settings', { theme: 'dark', lang: 'vi' });
const settings = StorageHelper.get('settings', { theme: 'light', lang: 'en' });
Đồng bộ dữ liệu giữa các tab
Một tính năng thú vị của LocalStorage là event storage — nó tự động fire khi dữ liệu thay đổi từ một tab khác:
// Tab A: cập nhật dữ liệu
localStorage.setItem('theme', 'dark');
// Tab B: lắng nghe thay đổi
window.addEventListener('storage', (event) => {
if (event.key === 'theme') {
console.log(`Theme changed from "${event.oldValue}" to "${event.newValue}"`);
document.body.className = event.newValue;
}
});
⚠️ Lưu ý quan trọng:
- Event
storagechỉ fire trên các tab khác, không phải tab đang gọisetItem() - Event không fire nếu giá trị mới giống hệt giá trị cũ
- Event chỉ fire khi cùng origin (protocol + domain + port)
Ứng dụng thực tế: Chat realtime đơn giản
// Gửi tin nhắn qua LocalStorage
function sendMessage(channel, message) {
const chatData = JSON.parse(localStorage.getItem(channel) || '[]');
chatData.push({ text: message, time: Date.now(), sender: 'me' });
localStorage.setItem(channel, JSON.stringify(chatData));
}
// Nhận tin nhắn từ tab khác
window.addEventListener('storage', (event) => {
if (event.key === 'chat-room-1' && event.newValue) {
const messages = JSON.parse(event.newValue);
renderMessages(messages);
}
});
Giới hạn dung lượng và lỗi thường gặp
Mỗi trình duyệt có giới hạn dung lượng khác nhau:
| Trình duyệt | Giới hạn LocalStorage |
|---|---|
| Chrome | ~10MB |
| Firefox | ~10MB |
| Safari | ~5MB |
| Edge | ~10MB |
Khi vượt quá giới hạn, trình duyệt sẽ ném lỗi QuotaExceededError:
try {
localStorage.setItem('big-data', largeString);
} catch (error) {
if (error.name === 'QuotaExceededError') {
console.warn('LocalStorage đầy! Cần xóa dữ liệu cũ.');
// Implement cleanup strategy
cleanupOldData();
}
}
Chiến lược quản lý dung lượng
function safeSetStorage(key, value, maxItems = 100) {
try {
localStorage.setItem(key, JSON.stringify(value));
return true;
} catch (e) {
if (e.name === 'QuotaExceededError') {
// Giữ lại chỉ N item mới nhất
const items = Array.isArray(value) ? value : [value];
const trimmed = items.slice(-maxItems);
localStorage.setItem(key, JSON.stringify(trimmed));
console.warn(`Đã cắt giảm ${key} xuống còn ${maxItems} items`);
return true;
}
return false;
}
}
LocalStorage vs Cookie vs IndexedDB
Khi nào nên dùng giải pháp nào?
| Tiêu chí | LocalStorage | Cookie | IndexedDB |
|---|---|---|---|
| Dung lượng | ~5-10MB | ~4KB | Không giới hạn |
| Tự gửi lên server | ❌ | ✅ | ❌ |
| Async | ❌ (sync) | ❌ (sync) | ✅ (async) |
| Tìm kiếm/query | ❌ | ❌ | ✅ |
| Hỗ trợ object phức tạp | JSON string | String | Any (structured clone) |
| Phù hợp cho | Settings, cache đơn giản | Session, auth tokens | Dataset lớn, offline apps |
Quy tắc chung:
- Cookie: Khi cần gửi dữ liệu lên server (auth, session)
- LocalStorage: Khi cần lưu dữ liệu client-side đơn giản, dung lượng vừa phải
- IndexedDB: Khi cần lưu dataset lớn (>10MB), tìm kiếm, hoặc app offline phức tạp
Best Practices
1. Luôn wrap trong try/catch
// ❌ Nguy hiểm — crash app nếu storage unavailable
const data = JSON.parse(localStorage.getItem('config'));
// ✅ An toàn — fallback graceful
let config;
try {
config = JSON.parse(localStorage.getItem('config')) || defaultConfig;
} catch (e) {
console.warn('LocalStorage không khả dụng, dùng default config');
config = defaultConfig;
}
2. Kiểm tra hỗ trợ trước khi dùng
function isStorageAvailable(type) {
try {
const storage = type === 'local' ? localStorage : sessionStorage;
const x = '__storage_test__';
storage.setItem(x, x);
storage.removeItem(x);
return true;
} catch (e) {
return false;
}
}
if (!isStorageAvailable('local')) {
console.warn('LocalStorage không được hỗ trợ!');
}
3. Không lưu dữ liệu nhạy cảm
// ❌ TUYỆT ĐỐI KHÔNG
localStorage.setItem('password', userPassword);
localStorage.setItem('accessToken', longToken);
// ✅ Nên dùng HttpOnly Cookie cho sensitive data
// Hoặc encrypt trước khi lưu (ít khuyến nghị)
import CryptoJS from 'crypto-js';
const encrypted = CryptoJS.AES.encrypt(data, secretKey).toString();
localStorage.setItem('encrypted-data', encrypted);
4. Đặt prefix cho keys
Tránh xung đột keys giữa các thư viện hoặc module:
const STORAGE_PREFIX = 'devs2_';
function setAppSetting(key, value) {
localStorage.setItem(`${STORAGE_PREFIX}${key}`, JSON.stringify(value));
}
function getAppSetting(key, fallback = null) {
const raw = localStorage.getItem(`${STORAGE_PREFIX}${key}`);
return raw ? JSON.parse(raw) : fallback;
}
5. Cleanup dữ liệu cũ định kỳ
function cleanupExpiredData() {
const now = Date.now();
const keysToRemove = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
const raw = localStorage.getItem(key);
try {
const parsed = JSON.parse(raw);
// Giả sử mỗi item có field 'expiresAt'
if (parsed.expiresAt && parsed.expiresAt < now) {
keysToRemove.push(key);
}
} catch (e) {
// Không phải JSON, bỏ qua
}
}
keysToRemove.forEach(key => localStorage.removeItem(key));
console.log(`Đã dọn ${keysToRemove.length} items hết hạn`);
}
// Chạy cleanup khi app khởi động
cleanupExpiredData();
Mẫu thực tế: Ứng dụng Todo List với LocalStorage
class TodoApp {
constructor() {
this.todos = this.loadTodos();
this.render();
}
loadTodos() {
const stored = localStorage.getItem('devs2_todos');
return stored ? JSON.parse(stored) : [];
}
saveTodos() {
localStorage.setItem('devs2_todos', JSON.stringify(this.todos));
}
addTodo(text) {
this.todos.push({
id: Date.now(),
text,
done: false,
createdAt: new Date().toISOString()
});
this.saveTodos();
this.render();
}
toggleTodo(id) {
const todo = this.todos.find(t => t.id === id);
if (todo) {
todo.done = !todo.done;
this.saveTodos();
this.render();
}
}
deleteTodo(id) {
this.todos = this.todos.filter(t => t.id !== id);
this.saveTodos();
this.render();
}
render() {
const container = document.getElementById('todo-list');
container.innerHTML = this.todos.map(todo => `
<li class="${todo.done ? 'done' : ''}">
<input type="checkbox" ${todo.done ? 'checked' : ''}
onchange="app.toggleTodo(${todo.id})">
<span>${todo.text}</span>
<button onclick="app.deleteTodo(${todo.id})">✕</button>
</li>
`).join('');
}
}
const app = new TodoApp();
Tổng kết
LocalStorage và SessionStorage là hai công cụ cực kỳ hữu ích cho hầu hết các ứng dụng web thông thường. Dưới đây là tóm tắt nhanh:
✅ Nên dùng khi:
- Lưu settings, preferences của người dùng
- Cache dữ liệu tạm thời để tăng performance
- Duy trì state giữa các page reload
- Đồng bộ dữ liệu đơn giản giữa các tab
❌ Không nên dùng khi:
- Lưu mật khẩu, token xác thực nhạy cảm
- Thay thế database server
- Lưu dataset lớn (>10MB)
- Khi cần dữ liệu được gửi lên server mỗi request
Hãy nhớ áp dụng các best practices: luôn wrap trong try/catch, kiểm tra hỗ trợ, đặt prefix cho keys, và đừng bao giờ lưu dữ liệu nhạy cảm. Với những nguyên tắc này, LocalStorage và SessionStorage sẽ trở thành đồng đội đáng tin cậy trong toolkit frontend của bạn.
Nếu bạn cần lưu trữ dữ liệu phức tạp hơn hoặc dung lượng lớn hơn, hãy khám phá IndexedDB — nhưng đó là câu chuyện cho một bài viết khác! 🚀