localStorage và sessionStorage: lưu dữ liệu ngay trong trình duyệt

  • author-image

    Nguyễn Đình Cường

  • blog-tag
  • blog-comment 0 comment
  • created-date 09 Nov, 2026
blog-thumbnail

Bạn muốn ứng dụng danh sách việc cần làm vẫn nhớ dữ liệu sau khi tải lại trang, hay chế độ sáng tối được giữ nguyên ở lần truy cập sau? Web Storage API cho phép lưu dữ liệu nhỏ ngay trong trình duyệt, không cần máy chủ, chỉ với vài dòng JavaScript.

Hai loại lưu trữ

  • localStorage: dữ liệu tồn tại lâu dài, vẫn còn sau khi đóng tab hoặc tắt trình duyệt, cho đến khi bị xóa.
  • sessionStorage: dữ liệu chỉ tồn tại trong một tab và mất khi tab đóng.

Cả hai gắn với origin (giao thức, tên miền và cổng), nên một trang không đọc được dữ liệu của trang thuộc origin khác. Dữ liệu lưu dưới dạng cặp khóa và giá trị, và cả hai luôn là chuỗi.

Các phương thức cơ bản

localStorage.setItem('theme', 'dark');          // ghi
const theme = localStorage.getItem('theme');     // đọc, null nếu không có
localStorage.removeItem('theme');                // xóa một khóa
localStorage.clear();                            // xóa toàn bộ

Vì chỉ nhận chuỗi, số và boolean sẽ bị chuyển thành chuỗi: lưu true rồi đọc lại bạn nhận được 'true', và chuỗi 'false' là truthy. Hãy cẩn thận khi kiểm tra.

Lưu object và mảng bằng JSON

Để lưu cấu trúc phức tạp, chuyển thành chuỗi JSON rồi chuyển ngược khi đọc:

const congViec = [
  { id: 1, ten: 'Học JS', xong: false },
  { id: 2, ten: 'Làm bài tập', xong: true },
];

localStorage.setItem('todos', JSON.stringify(congViec));

const daLuu = JSON.parse(localStorage.getItem('todos'));

Nếu khóa chưa tồn tại, getItem trả về null và JSON.parse(null) cho ra null. Dữ liệu bị hỏng sẽ khiến JSON.parse ném lỗi, nên hãy bọc an toàn.

Hàm tiện ích an toàn

Trình duyệt có thể chặn truy cập storage (chế độ riêng tư, người dùng tắt dữ liệu trang) hoặc dung lượng đầy, khiến lệnh ném lỗi. Hãy gói lại:

const kho = {
  doc(khoa, macDinh) {
    try {
      const v = localStorage.getItem(khoa);
      return v === null ? macDinh : JSON.parse(v);
    } catch (e) {
      return macDinh;
    }
  },
  ghi(khoa, giaTri) {
    try {
      localStorage.setItem(khoa, JSON.stringify(giaTri));
    } catch (e) {
      console.warn('Không lưu được', e);
    }
  },
};

const todos = kho.doc('todos', []);
kho.ghi('todos', todos);

Ứng dụng vẫn chạy được, chỉ là không nhớ dữ liệu, thay vì sập hoàn toàn khi storage không khả dụng.

Ví dụ: ghi nhớ chế độ sáng tối

const goc = document.documentElement;
goc.setAttribute('data-theme', kho.doc('theme', 'light'));

document.querySelector('#doi-theme').addEventListener('click', function () {
  const moi = goc.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
  goc.setAttribute('data-theme', moi);
  kho.ghi('theme', moi);
});

Giới hạn và điều không nên làm

  • Dung lượng nhỏ: mỗi origin thường chỉ có vài megabyte. Không dùng để lưu dữ liệu lớn như ảnh hay video.
  • Đồng bộ: các thao tác chặn luồng chính, nên đừng đọc ghi khối lượng lớn liên tục.
  • Không an toàn cho bí mật: mã JavaScript của trang, kể cả mã độc chèn vào qua lỗi XSS, đọc được toàn bộ storage. Đừng lưu mật khẩu, thẻ thanh toán hay dữ liệu nhạy cảm. Với token đăng nhập, hãy cân nhắc kỹ, nhiều đội ưu tiên cookie HttpOnly vì JavaScript không đọc được chúng.
  • Không đáng tin tuyệt đối: người dùng có thể xóa bất kỳ lúc nào, nên đây là bộ nhớ đệm tiện lợi, không phải cơ sở dữ liệu.

Đồng bộ giữa các tab

Khi một tab thay đổi localStorage, các tab khác cùng origin nhận được sự kiện storage:

window.addEventListener('storage', function (e) {
  if (e.key === 'theme') {
    document.documentElement.setAttribute('data-theme', e.newValue);
  }
});

Sự kiện chỉ kích hoạt ở các tab khác, không ở tab vừa thực hiện thay đổi.

Khi nào cần giải pháp khác?

Nếu cần lưu lượng dữ liệu lớn, truy vấn có cấu trúc hoặc làm việc ngoại tuyến, hãy tìm hiểu IndexedDB. Nếu dữ liệu cần xuất hiện trên nhiều thiết bị, bạn cần lưu ở máy chủ. Cookie phù hợp khi máy chủ cần đọc giá trị trong mỗi yêu cầu.

Ví dụ: danh sách việc cần làm nhớ dữ liệu

Ghép các phần trên lại thành khung của một ứng dụng todo, dùng hàm kho ở trên:

let todos = kho.doc('todos', []);

function them(ten) {
  todos.push({ id: Date.now(), ten, xong: false });
  luu();
}

function dao(id) {
  todos = todos.map(t => t.id === id ? { ...t, xong: !t.xong } : t);
  luu();
}

function luu() {
  kho.ghi('todos', todos);
}

Mỗi khi dữ liệu đổi, ta gọi luu() để ghi lại. Khi tải lại trang, kho.doc đưa danh sách trở về. Chỉ với khoảng hai chục dòng, ứng dụng của bạn đã có tính năng nhớ dữ liệu, một bước tiến lớn về trải nghiệm.

Kết luận

localStorage lý tưởng cho tùy chọn giao diện, bản nháp và dữ liệu nhỏ không nhạy cảm. Hãy luôn dùng JSON cho dữ liệu phức tạp, bọc trong try/catch và không bao giờ lưu bí mật ở đó.

Hãy thử áp dụng vào một ứng dụng todo của riêng bạn, hoặc xem các dự án thực hành trong khóa học trên codebem.com để có thêm ý tưởng.

author_photo
Nguyễn Đình Cường

0 comment