再谈浏览器存储之 localStorage 和 cookie

1,099 阅读3分钟

「这是我参与11月更文挑战的第 13 天,活动详情查看:2021最后一次更文挑战」。

WEB 项目的开发,不可避免的要设计存储,这里所说的主要是浏览器存储。之前在文章《WEB 本地存储:localStorage、Web SQL Database、IndexedDB》介绍过浏览器存储。本文之所有再谈浏览器存储,是希望从更加细致的角度出发,并分享存储相关的方法集合。

浏览器存储

在 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 的值才被触发。

总结

浏览器存储对于客户端存储数据非常有用,无需调用服务器数据,始终存储在用户浏览器端。