瀏覽器儲存之 localStorage 和 cookie

語言: CN / TW / HK

瀏覽器儲存之 localStorage 和 cookie

WEB 專案的開發,不可避免的要設計儲存,這裡所說的主要是瀏覽器儲存。在HTML5之前,應用程式資料只能儲存在 cookie 中,並且會包含在每個伺服器請求中。與 cookie 不同,瀏覽器本地儲存限制要大得多(至少5MB),並且資訊不會被傳輸到伺服器。本文將要介紹的本地儲存包括:localStorage cookie

瀏覽器儲存

在 React 或者 Vue 應用程式中,通常將資料儲存在 store 中,要麼使用元件狀態,要麼使用狀態管理工具,例如 ReduxVuexMobX 。這些由狀態驅動UI的工具很好用,當用戶重新整理頁面時,必須重新通過 API 獲取所有的資料並再次填充狀態。在很多情況下,可能希望以這樣一種方式持久化資料,即當用戶重新整理頁面時,一切都不會丟失。想象一下這樣一個場景:使用者每次重新整理頁面時都必須重新進行身份驗證!

本文將詳細介紹在瀏覽器中儲存資料的三種方式:localStoragesessionStoragecookies ,而關於 IndexedDB 後續將專門詳細介紹和使用指南。

瀏覽器儲存概述

  • localStorage:完全存在於客戶端(瀏覽器)中的鍵/值儲存。用於儲存不需要傳送到伺服器的身份驗證 Token 或者離線資料。
  • sessionStorage:一種鍵/值儲存,其功能與 localStorage 類似,但在使用者關閉頁面時過期/清除,即使在同一域中也不會跨選項卡共享,最適用於儲存臨時資料。
  • cookie:可以在瀏覽器中讀取和寫入的資料,但也會隨著每個請求的 cookie header 中的傳輸到伺服器。
  • IndexDB:一個存在於瀏覽器中的資料庫,單獨使用有點困難,但與 PouchDB 等工具配合使用,可用於儲存需要查詢和效能要求的更復雜的客戶端資料。

localStorage 和 sessionStorage

localStoragesessionStorage 都是使用 Storage 物件的鍵/值儲存,並且鍵和值都必須是字串。

// Set值
localStorage.setItem("name", "DevPoint");
// Get值
localStorage.getItem("name"); // "DevPoint"
// Delete值
localStorage.removeItem("name");
// Clear所有的值
localStorage.clear();

前面提到的鍵和值都必須是字串,那麼如何用於儲存陣列或物件呢?這時就需要使用 JSON.stringify(object) 將 JS 物件轉換為 JSON 字串,並在獲取值的時候使用 JSON.parse(string)JSON 字串轉換回 JS 物件。下面是一個完整的處理方法集:

const useStorage = (storageKey = "authorization") => {
    const localKey = `devpoint.local.${storageKey}`;

    const toJson = (str) => {
        try {
            return JSON.parse(str);
        } catch {
            return str;
        }
    };
    const toStringfy = (value)=>{
        try {
            return JSON.stringify(value);
        } catch {
            return value;
        }
    }

    const save = (data) => {
        window.localStorage.setItem(localKey, JSON.stringify(data));
    };

    const get = () => {
        const localData = window.localStorage.getItem(localKey);
        if (localData && localData !== "") {
            return toJson(localData);
        } else {
            return false;
        }
    };
    /**
     * Delete
     */
    const del = () => {
        window.localStorage.removeItem(localKey);
    };
    /**
     * 清除所有的 localStorage
     */
    const clear = () => {
        window.localStorage.clear();
    };
    // 返回儲存物件處理方法
    return {
        save,
        get,
        del,
        clear,
    };
};
const storageAuth = useStorage();
const loginInfo = {
    username: "DevPoint",
    age: 30,
};
storageAuth.save(loginInfo);
console.log(storageAuth.get());

Cookies

HTTP Cookie(也叫 Web Cookie 或瀏覽器 Cookie)是伺服器傳送到使用者瀏覽器並儲存在本地的一小塊資料,它會在瀏覽器下次向同一伺服器再發起請求時被攜帶併發送到伺服器上。是WEB專案開發需要涉及的內容。

如果應用程式是完全客戶端 SPA(單頁應用程式),可能不需要 cookie,使用 localStorage 可以解決問題。如果是使用 Next.js 或者 Node.js 提供伺服器介面需要身份驗證 Token 的可以考慮使用 cookie

通常認為 cookie 是複數形式,但事實是它們儲存在單個字串值中,必須對其進行解析才能將它們分解為單獨的鍵/值對

console.log(document.cookie); // _gcl_au=1.1.1660316496.1636468606; _ga=GA1.2.221099298.1636468607; _gid=GA1.2.1474751041.1636468607;

可以通過 ; 拆分字串來將它們分開,然後對映每個值並使用 = 將其拆分為 ,最終將得到相應的鍵/值對。下面是一個完整的方法集:

const useCookie = (options = { days: 30, path: "/" }) => {
    const { days: expiresDays, path: cookiePath } = options;

    const set = (name, value) => {
        const exp = new Date();
        exp.setTime(exp.getTime() + expiresDays * 24 * 60 * 60 * 1000);
        const strExpires = exp.toGMTString();
        const cookieValue = escape(value);
        document.cookie = `${name}=${cookieValue};expires=${strExpires};path=${cookiePath}`;
    };
    const get = (name) => {
        let arr;
        const reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)");
        if ((arr = document.cookie.match(reg))) {
            return unescape(arr[2]);
        } else {
            return null;
        }
    };
    // 刪除cookie
    const remove = (name) => {
        document.cookie = name + "=;expires=" + new Date(0).toGMTString();
    };
    // 清除所有 cookie
    const clear = () =>
        document.cookie
            .split(";")
            .forEach(
                (cookie) =>
                    (document.cookie = cookie
                        .replace(/^ +/, "")
                        .replace(
                            /=.*/,
                            `=;expires=${new Date().toUTCString()};path=${cookiePath}}`
                        ))
            );
    /**
     * 獲取所有的 cookie
     * @returns
     */
    const all = () =>
        document.cookie
            .split(";")
            .map((item) => item.split("="))
            .reduce(
                (acc, [k, v]) => (acc[k.trim().replace('"', "")] = v) && acc,
                {}
            );
    return {
        set,
        get,
        clear,
        remove,
        all,
    };
};
const cookieHelper = useCookie();
cookieHelper.set("name", "DevPoint");
cookieHelper.set("city", "Shenzhen");
console.log(cookieHelper.get("name")); // DevPoint
console.log(cookieHelper.all()); // { name: "DevPoint", city: "Shenzhen" }
cookieHelper.remove("name");
console.log(cookieHelper.all()); // { city: "Shenzhen" }

出於安全考慮,某些 cookie 可能被標記為 僅 HTTP ,這意味著此類 cookie 不能從客戶端的 JavaScript 程式碼中獲取到。

儲存事件

在上面看到了一些非常簡潔的東西,localStoragesessionStorage 還有一個儲存或者更新值時進行監聽的事件 storage ,然而觸發監聽事件需要滿足一下兩個條件:

  1. 通過 localStorage.setItemsessionStorage.setItem 儲存(更新)某個storage
  2. 儲存(更新)這個 storage 時,它的新值必須與之前的值不同
window.addEventListener("storage", (event) => {
    console.log(event);
});

事件 event 是一個 StorageEvent 物件,

  • oldValue:更新前的值。如果該鍵為新增加,則這個屬性為 null
  • newValue:更新後的值,如果鍵被刪除,則這個屬性為 null
  • url:原始觸發 storage 事件的頁面網址。
  • key:更新的儲存鍵

需要特別注意的是,事件StorageEvent 不會在當前頁面觸發,只有在瀏覽器開啟同一個域名下的多個頁面,其它頁面操作改變了 localStoragesessionStorage 的值才被觸發。

總結

瀏覽器儲存對於客戶端儲存資料非常有用,無需呼叫伺服器資料,始終儲存在使用者瀏覽器端。