Skip to main content
JavaScript 5 mins read Devs2.org

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ế.

#JavaScript #LocalStorage #SessionStorage #Web Storage #Frontend #Browser API

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 — LocalStorageSessionStorage chính là hai công cụ mạnh mẽ nhất mà JavaScript cung cấp cho mục đích này.

LocalStorage và SessionStorage - Hướng dẫn toàn diện về lưu trữ dữ liệu trên browser

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ả LocalStorageSessionStorage đề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ăngLocalStorageSessionStorage
Thời gian sốngVĩ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
ScopeDomain (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 storage chỉ fire trên các tab khác, không phải tab đang gọi setItem()
  • 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ệtGiớ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;
  }
}

Khi nào nên dùng giải pháp nào?

Tiêu chíLocalStorageCookieIndexedDB
Dung lượng~5-10MB~4KBKhô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ạpJSON stringStringAny (structured clone)
Phù hợp choSettings, cache đơn giảnSession, auth tokensDataset 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! 🚀

Recently Used Tools