Trong các ứng dụng web, dữ liệu thường được lưu tại các cơ sở dữ liệu được tổ chức bài bản như MySQL, MongoDB, PostgresQL, v/v. Tuy nhiên, trong một vài trường hợp, dữ liệu không cần lưu giữ dài hạn tại một cơ sở dữ liệu và có thể được giữ trực tiếp ngay trên trình duyệt của người dùng. Để lưu trữ dữ liệu trên trình duyệt, ta có thể dùng một trong hai API chính của browser: localStoragesessionStorage.

Cả hai tính năng trên hoạt động khá tương đồng, dữ liệu được lưu dưới dạng các cặp key/value. Các thông tin được lưu trữ trên sessionStorage có thể tồn tại sau khi tải lại trang (page refresh), trong khi đối với localStorage, dữ liệu sẽ được giữ lại ngay cả khi tắt và mở lại trình duyệt (browser restart).

localStoragesessionStorage đều có các methods và thuộc tính tương tự nhau:

  • setItem(key, value) – ghi các cặp key/value và bộ nhớ
  • getItem(key) – truy xuất dữ liệu bằng key
  • removeItem(key) – xoá key/value trên bộ nhớ
  • clear() – xoá toàn bộ
  • key(index) – lấy một key dựa trên thứ tự
  • length – số lượng giá trị đang được lưu

Có thể thấy rằng, localStoragesessionStorage có cấu trúc dạng Map với setItem/getItem/removeItem và cũng có thể truy xuất dữ liệu bằng index với key(index).

Sử dụng localStorage

Đặc điểm chính của localStorage là:

  • Dữ liệu có thể được truy cập từ nhiều tabs khác nhau của cùng một domain (origin)
  • Các dữ liệu được lưu là không có thời hạn. Dữ liệu vẫn tồn tại sau khi khởi động lại trình duyệt hoặc khởi đồng lại máy tính.

Để hiểu rõ hơn, chạy đoạn code sau:

localStorage.setItem('test', 1);

Sau đó, đóng và mở lại trình duyệt, hoặc tải trang trên một tab khác và chạy đoạn code sau:

alert( localStorage.getItem('test') ); // Kết quả: 1

Lưu ý, dữ liệu sẽ được chia sẻ trên các tabs có cùng origin (domain/port/protocol), phần đường dẫn phía sau có thể khác nhau. Vì dữ liệu được chia sẻ qua các tabs, khi ta ghi dữ liệu trên một tab, các tabs khác cũng sẽ thấy được sự thay đổi.

Object-like access

Ngoài cách sử dụng method setItem/getItem/removeItem, ta còn có thể truy xuất dữ liệu được lưu trữ trên localStorage như một object thông thường:

// set key
localStorage.test = 2;

// get key
alert( localStorage.test ); // Kết quả: 2

// remove key
delete localStorage.test;

Khi sử dụng các này để làm việc với localStorage, ta cần lưu ý các điểm sau:

  • Một số key như length hay toString là các method sẵn có của localStorage, nếu dùng theo dạng object, có thể ta sẽ gặp lỗi. Nhưng nếu dùng với setItem/getItem thì sẽ không có vấn đề gì nghiêm trọng:
let key = 'length';
localStorage[key] = 5; // Error, không thể gán length cho localStorage
  • Ngoài ra, mỗi khi dữ liệu trên localStorage thay đổi, trình duyệt sẽ kích hoạt storage event. Các event này sẽ không được kích hoạt nếu là dùng theo dạng object.

Vòng lặp trên các keys

Ta có thể setItem/getItem/removeItem với localStorage để xem hoặc ghi thông tin cho từng key. Làm cách nào để xem tất cả dữ liệu đang được lưu trên localStorage?

Ta có thể thực hiện bằng cách dùng vòng lặp trên tất cả các key của localStorage (vì localStorage không cung cấp tính năng lặp trực tiếp trên các dữ liệu được lưu):

for(let i=0; i<localStorage.length; i++) {
  let key = localStorage.key(i);
  alert(`${key}: ${localStorage.getItem(key)}`);
}

Hoặc sử dụng cú pháp for key in localStorage (cách này sẽ hiển thị cả những method sẵn có của localStorage như getItem/setItem:

for(let key in localStorage) {
  alert(key); // có cả getItem, setItem và các key sẵn có khác
}

Để loại các thuộc tính và method sẵn có, ta dùng hasOwnProperty:

for(let key in localStorage) {
  if (!localStorage.hasOwnProperty(key)) {
    continue; // loại các key "setItem", "getItem", v/v
  }
  alert(`${key}: ${localStorage.getItem(key)}`);
}

Các ngắn gọn hơn, ta dùng Object.keys để lặp qua các key của localStorage (các method và thuộc tính sẵn có sẽ không được hiển thị):

let keys = Object.keys(localStorage);
for(let key of keys) {
  alert(`${key}: ${localStorage.getItem(key)}`);
}

Lưu các dạng dữ liệu khác trên localStorage

Có một điều lưu ý, tất cả dữ liệu được lưu trữ trên localStorage đều có dạng string. Nếu ta cố gắng lưu các dạng dữ liệu khác, nó sẽ được chuyển đổi sang dạng string một cách tự động:

localStorage.user = {name: "John"};
alert(localStorage.user); // [object Object]

Để giải quyết vấn đề này, ta có thể dùng JSON để lưu các dạng dữ liệu object:

localStorage.user = {name: "John"};
alert(localStorage.user); // [object Object]

Ta có thể thực hiện bằng cách dùng vòng lặp trên tất cả các key của localStorage (vì localStorage không cung cấp tính năng lặp trực tiếp trên các dữ liệu được lưu):

localStorage.user = JSON.stringify({name: "John"});

// sometime later
let user = JSON.parse( localStorage.user );
alert( user.name ); // John

sessionStorage

sessionStorage có các thuộc tính và method tương đương như localStorage với một số điểm hạn chế:

  • sessionStorage chỉ tồn tại trên tab hiện tại:
    • Các tabs khác của cùng origin sẽ có sessionStorage riêng
    • Nếu trên một tabs có sử dụng iframe cho cùng một origin, sessionStorage sẽ được chia sẻ.
  • Dữ liệu có thể tồn tại sau khi tải lại trang, nhưng sẽ mất nếu đóng tab.

Vì các điểm hạn chế như trên, sessionStorage thường ít được sử dụng hơn localStorage. Để xem thử cách hoạt động của sessionStorage, chạy đoạn code sau:

sessionStorage.setItem('test', 1);

Sau đó, tải lại trang và chạy đoạn code sau:

alert( sessionStorage.getItem('test') ); // Sau khi tải lại trang: 1

Nếu bạn chạy đoạn code ngay trên trong một tab khác, giá trị nhận được sẽ là null.

Storage event

Khi dữ liệu trên localStorage hoặc sessionStrorage được cập nhật, storage event sẽ được kích hoạt với các thuộc tính sau:

  • key – key đươc cập nhật (null nếu .clear() được dùng)
  • oldValue – giá trị cũ (null nếu key mới được được tạo)
  • newValue – giá trị mới (null nếu key bị xoá)
  • url – đường dẫn của trang tại nơi sự thay đổi diễn ra
  • storageArealocalStorage hoặc sessionStorage

Một lưu ý quan trọng: event chỉ được kích hoạt tại các tabs có thể truy cập vào storage (cùng origin) trừ tab nơi sự thay đổi diễn ra.

Ví dụ, ta có hai tabs chạy cùng một trang web. localStorage sẽ được chia sẻ giữa hai tab trên. Nếu cả hai tab đề được cài đặt để đợi event window.onstorage, mỗi tab sẽ chỉ nhận được event khi sự thay đổi diễn ra tại tab còn lại.

Để hiểu vấn đề này, bạn có thể chạy đoạn code sau trên hai tabs khác nhau với cùng một trang web:


window.onstorage = event => { 
  if (event.key != 'now') return;
  alert(event.key + ':' + event.newValue + " at " + event.url);
};

localStorage.setItem('now', Date.now());

event.url là đường dẫn tại trang mà sự thay đổi diễn ra. Với tính năng storage event này, ta có thể tạo ra sự giao tiếp giữa các tabs của cùng một trang web trên trình duyệt.

Tạm kết

localStoragesessionStorage có phép lưu trữ dữ liệu dưới dạng các cặp key/value trên trình duyệt:

  • Cả keyvalue đều phải có dạng string
  • Kích thước không quá 5MB, tuỳ vào trình duyệt
  • Không có thời hạn
  • Dữ liệu được chia sẻ trên các tabs có cùng origin (domain/port/protocol)

Điểm khác nhau:

  • localStorage:
    • Chia sẽ giữa các tabs và của sổ có cùng origin
    • Tồn tại ngay cả sau khi khởi động lại trình duyệt
  • sessionStorage:
    • Chỉ tồn tại trên một tab, bao gồm iframe trong tab đó với cùng origin
    • Có thể tồn tại sau khi tải lại trang

API:

  • setItem(key, value) – ghi các cặp key/value và bộ nhớ
  • getItem(key) – truy xuất dữ liệu bằng key
  • removeItem(key) – xoá key/value trên bộ nhớ
  • clear() – xoá toàn bộ
  • key(index) – lấy một key dựa trên thứ tự
  • length – số lượng giá trị đang được lưu
  • Dùng Object.keys để xem tất cả key đã lưu
  • Có thể cập nhật dữ liệu theo dạng object với key, trong trường hợp này event sẽ không được kích hoạt

Storage event:

  • Kích hoạt khi các method sau được gọi: setItem, removeItemclear()
  • Chứa các thông tin về tác vụ được thực hiện (key/oldValue/newValue), đường dẫn của tab urlstorageArea (localStorage hoặc sessionStorage)
  • Được kích hoạt tại tất cả tabs và của sổ được chia sẻ storage, trừ tab nơi sự thay đổi diễn ra.