我所知道的HTML——本地存储

215 阅读8分钟

往期回顾

  1. 我所知道的HTML——现成轮子都堆成山了,新特性还算新吗?(上篇)
  2. 我所知道的HTML——现成轮子都堆成山了,新特性还算新吗?(中篇)
  3. 我所知道的HTML——现成轮子都堆成山了,新特性还算新吗?(下篇)
  4. 我所知道的HTML——跨源资源共享(CORS)

(如果您正巧因为首页推荐的功能点进此文章,由衷地建议您先回顾往期内容,这将有助您接下来的阅读体验。)

QQ图片20240823121106.png

前言

在上一篇文章中,我们讨论了跨源资源共享CORS,学习了它的定义、使用场景、限制和一些HTTP头,并且结合真实的面试题,将理论知识应用于实践。

在本篇文章中,我们会一起学习本地存储:

  • sessionStorage
  • localStorage

sessionStorage

sessionStorage 属性允许你访问一个,对应当前的 session Storage 对象。它与 localStorage 相似,不同之处在于 localStorage 里面存储的数据不存在过期时间,而存储在 sessionStorage 里面的数据在页面会话结束时会被清除(即当前页面被关闭后)。——MDN

语法

sessionStorage的使用很简单,只有4种方式,分别是保存数据、获取数据、删除(保存的)数据以及删除全部(保存的)数据

  • 保存数据:sessionStorage.setItem("userName", "Mike");
    • 第一个参数是键名【key】
    • 第二个参数是键值【value】
  • 获取数据:sessionStorage.getItem("userName");
    • 参数为键名
  • 删除数据:sessionStorage.removeItem("userName");
    • 参数为键名
  • 删除全部数据:sessionStorage.clear()

关于sessionStorage.setItem方法,还有一个需要注意的地方,那就是键名和键值的格式:

被存储的键值对总是以 UTF-16 DOMString 的格式所存储,其使用两个字节来表示一个字符。对于对象、整数 key 值会自动转换成字符串形式。

我们来根据这个格式,写一个代码示例看看效果:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Storage Example</title>

</head>

<body>

    <button id="btn">Click Me</button>
    <script>
        // 对象作为键,数组作为值
        var key_obj = { a: 1, b: 234, c: "hello" }
        var value_obj = [1, 2, 3, 4, 5, 6]

        sessionStorage.setItem(key_obj, value_obj);

        // 整数作为键,整数作为值
        var key_num = 1
        var value_num = 12

        sessionStorage.setItem(key_num, value_num);

    </script>
</body>

</html>

image.png

可以看到,对象的存储结果变成了[object Object]

如果我们期望对象的内容可以被正常存入,那么可以使用JSON.stringify

    <script>

        // 对象作为键,数组作为值
        var key_obj = { a: 1, b: 234, c: "hello" }
        var value_obj = [1, 2, 3, 4, 5, 6]

        sessionStorage.setItem(JSON.stringify(key_obj), value_obj);
     </script>

image.png

不过JSON.stringify是有很多局限性的,比如:

  1. 不支持的值JSON.stringify 无法序列化以下类型的值:

    • undefinedFunction 和 Symbol 类型的值在序列化过程中会被忽略或转换成 null
    • 不可枚举的属性会被忽略。
    • 如果对象中存在循环引用,JSON.stringify 会抛出错误。
  2. 格式限制JSON.stringify 仅支持 JSON 格式允许的数据类型,这意味着:

    • 所有键名都必须是字符串。
    • 不支持 Date 对象,它会被转换成一个字符串而不是日期对象。
    • 不支持 BigInt 类型,因为 JSON 标准不支持大于 2^53 - 1 的整数。
  3. 自定义序列化:默认情况下,JSON.stringify 不会调用对象的自定义 toJSON 方法。如果需要自定义序列化行为,必须显式调用对象的 toJSON 方法。

  4. 格式化限制:虽然 JSON.stringify 允许第二个参数来替换值,或者第三个参数来添加缩进,但它对格式的控制是有限的。例如,不能自定义日期格式或添加注释。

  5. 性能问题:当处理大量数据或深层嵌套的对象时,JSON.stringify 可能会变得很慢,因为它需要递归遍历整个对象结构。

  6. 顺序问题JSON.stringify 不保证对象的键值对的顺序。在 ES2015(ES6)及以后的版本中,对象键的顺序是根据其添加到对象中的顺序来确定的,但 JSON.stringify 可能不会保留这个顺序。

所以当我们保存对象时,需要注意呢。

对象的存储结果变成了[object Object],本质上可以认为是默认调用了toString()方法,因此如果我们给key_obj添加一个toString去遮蔽掉原型链上的toString()方法的话,我们可以实现这种效果

    <script>
        // 对象作为键,数组作为值
        var key_obj = { a: 1, b: 234, c: "hello", toString: () => "hello" }
        var value_obj = [1, 2, 3, 4, 5, 6]

        sessionStorage.setItem(key_obj, value_obj);


    </script>

image.png

可以看到此时存的键就是"hello"

性质

  • 页面会话在浏览器打开期间一直保持,并且重新加载恢复页面仍会保持原来的页面会话。

    • 这个性质从下方动图可见一斑: session存储.gif
  • 在新标签或窗口打开一个页面时会复制顶级浏览会话的上下文作为新会话的上下文,这点和 session cookie 的运行方式不同。

    1. 新标签或窗口打开页面: 当你在浏览器中通过新标签或窗口打开一个页面时,这个新打开的页面会创建一个新的浏览上下文。这个上下文是独立的,意味着它有自己的历史记录、DOM 状态、JavaScript 变量等。

    2. 复制顶级浏览会话的上下文: 虽然新标签或窗口创建了一个新的浏览上下文,但是它会复制顶级浏览会话(即浏览器主窗口的会话)的某些上下文信息。例如,如果是通过浏览器的主窗口打开新标签或窗口,新标签或窗口可能会继承顶级窗口的一些设置,如用户登录状态、权限设置等。但这并不意味着两个上下文是完全相同的,它们仍然是独立的。

    3. 与 session cookie 的运行方式不同: session cookie 是一种特殊的 cookie,它在用户会话期间存在,通常用于存储用户登录状态等信息。当用户关闭浏览器时,session cookie 就会被删除。重要的是,session cookie 是绑定到浏览器会话的,而不是绑定到特定的标签或窗口。

      这里的关键区别在于:

      • 当你在一个新标签或窗口中打开页面时,新会话可能会复制顶级浏览会话的某些上下文,但这通常不包括 session cookie 的状态。每个标签或窗口通常都有自己的 session cookie,除非它们是从同一个标签或窗口直接打开的(例如,通过 JavaScript 的 window.open 方法)。
      • 因此,如果你在新标签或窗口中打开一个页面,并且希望保持用户登录状态,你不能依赖于 session cookie 的自动复制。相反,你需要确保在新标签或窗口中也能正确设置 session cookie。

    简而言之,这句话强调了在新标签或窗口打开页面时,尽管会复制一些顶级浏览会话的上下文,但这个过程与 session cookie 的行为不同,因为 session cookie 通常不会在新标签或窗口中自动复制。

  • 打开多个相同的 URL 的 Tabs 页面,会创建各自的 sessionStorage

    • 这张页面没有点击按钮,因此不会往sessionStorage中存入btn-13579这个键值对 image.png

    • 这张页面点击了按钮,往sessionStorage中存入btn-13579这个键值对 image.png 可以看到,两张页面URL相同,但是sessionStorage是独立的,互不影响。

  • 关闭对应浏览器标签或窗口,会清除对应的 sessionStorage

localStorage

只读的localStorage 属性允许你访问一个Document 源(origin)的对象 Storage;存储的数据将保存在浏览器会话中。localStorage 类似 sessionStorage,但其区别在于:存储在 localStorage 的数据可以长期保留;而当页面会话结束——也就是说,当页面被关闭时,存储在 sessionStorage 的数据会被清除。

语法

其实和sessionStorage一模一样。localStorage的使用同样很简单,只有4种方式,分别是保存数据、获取数据、删除(保存的)数据以及删除全部(保存的)数据

  • 保存数据:localStorage.setItem("userId", "0823");

    • 第一个参数是键名【key】
    • 第二个参数是键值【value】
  • 获取数据:localStorage.getItem("userId");

    • 参数为键名
  • 删除数据:localStorage.removeItem("userId");

    • 参数为键名
  • 删除全部数据:localStorage.clear()

同理,需要注意键名和键值的格式:

被存储的键值对总是以 UTF-16 DOMString 的格式所存储,其使用两个字节来表示一个字符。对于对象、整数 key 值会自动转换成字符串形式。

在此不做赘述了。

性质

  1. 如果不进行手动清空,比如通过F12打开控制台,手动清空。哪怕浏览器被关闭了,或者电脑重启了,这个下的localStorage照样存在。

  2. 之前我们提到不同的页面会话会对应各自的sessionStorage,但是localStorage是被共享的,我们看一个例子:

    local存储.gif 可以看到,两个不同的标签页,当触发新增和删除操作时,localStorage同步改变

本地存储的安全性

论数据存储在 localStorage 还是 sessionStorage ,它们都特定于页面的协议。

意思就是受到同源策略的保护,即不同源的网页不能彼此访问各自的 localStorage 和 sessionStorage

结语

最后,我们会再聊一聊manifest,将其作为HTML系列文章的结尾,manifest是实现渐进式 Web 应用(PWA:Progressive Web App)的关键组成部分之一。在聊PWA的时候,我们也会提一下Service Worker,它能够帮助PWA在没有网络连接的情况下(即离线状态下)工作,比如离线阅读某些新闻,通过Service WorkerCacheClientsFetchEvent等API实现。