Web Storage

avatar
前端工程师 @豌豆公主

Web Storage (客户端存储)

storage (1).png

从cookie 说起

  • Cookie 的本职工作并非本地存储,而是“维持状态”。

  • HTTP 协议是一个无状态协议,服务器接收客户端的请求,返回一个响应,故事到此就结束了,服务器并没有记录下关于客户端的任何信息。那么下次请求的时候,如何让服务器知道“我是我”呢?

在这样的背景下,Cookie 应运而生。

  • 大家知道,Cookie 是有体积上限的,它最大只能有 4KB。当 Cookie 超过 4KB 时,它将面临被裁切的命运。这样看来,Cookie 只能用来存取少量的信息。
  • cookie有一些限制,例如每个域名下最多只能存储20个cookie,每个cookie的大小不能超过4KB,且cookie可以被浏览器禁用或删除。

Web Storage

  • 是 HTML5 专门为浏览器存储而提供的数据存储机制。它又分为 Local Storage 与 Session Storage。这两组概念非常相近,我们不妨先理解它们之间的区别,再对它们的共性进行研究。

Local Storage VS Session Storage

sessionStorage

  • 为每一个给定的源(given origin)维持一个独立的存储区域。该存储区域在页面会话期间可用(即只要浏览器处于打开状态,包括页面重新加载和恢复)-会话存储。

localStorage

  • 同样的功能,但是在浏览器关闭,然后重新打开数据仍然存在 - 持久化存储。

sessionStorage 和localStorage 一般统称为 Web Storage (API)

相同点

  • 都遵循同源策略
  • 容量一样

不同点

生命周期作用域的不同。

  • 生命周期:Local Storage 是持久化的本地存储,存储在其中的数据是永远不会过期的,使其消失的唯一办法是手动删除;而 Session Storage 是临时性的本地存储,它是会话级别的存储,当会话结束(页面被关闭)时,存储内容也随之被释放。

  • 作用域:Local Storage、Session Storage 和 Cookie 都遵循同源策略。但 Session Storage 特别的一点在于,即便是相同域名下的两个页面,只要它们不在同一个浏览器窗口中打开,那么它们的 Session Storage 内容便无法共享。

webStorage注意事项

  • 都是同步API,阻塞,如果存单个键或者值太大,影响DOM
  • 存储的是字符串,要保存对象时,通常需要JSON.Stringgfy

webStorage是共享的吗

test1

<button type="button" id="btnAdd">添加</button>
<a href="./other.html" target="_blank">打开新页面</a>

test2

<button type="button" id="btnAdd">添加</button>
<a href="./other.html" target="_blank" rel="opener">打开新页面</a>

总结

  • 打开多个相同的URL 的Tab,会创建各自的sessionStorage
  • 通过a标签target="blank"跳转到新页面时 sessionStorage就会丢失。a标签添加属性rel="opener'"能够复制 。仅仅能复制,之后的更改并不会同步!!
  • rel="opener' => 表示当打开链接文档时,应在与当前浏览上下文相关的新浏览上下文中打开它

webStorage过期支持

  • 添加一个属性,记住过期时间
  • 添加数据,一起保存
  • 查询数据,对比时间,过期删除

代码

html

<button type="button" id="btnSetItem">添加</button>
<button type="button" id="btnGetItem">查询</button>

js

 const myLocalStore = {
        setItem: (key, value, expire) => {
          const lsValue = JSON.parse(localStorage.getItem(key) || "{}");
          localStorage.setItem(
            key,
            JSON.stringify({
              ...lsValue,
              value,
              expire,
            })
          );
        },
        getItem: (key) => {
          // 在取值之前先判断是否过期
          const lsValue = JSON.parse(localStorage.getItem(key) || "{}");
          if (lsValue.expire && lsValue.expire >= Date.now()) {
            return lsValue.value;
          } else {
            localStorage.removeItem(key);
            return null;
          }
        },
      };
      btnSetItem.onclick = function () {
        myLocalStore.setItem("key-x", "value-1", Date.now() + 10000);
      };
      btnGetItem.onclick = function () {
        console.log("getItem:", myLocalStore.getItem("key-x"));
      };

第三方库

  • we-storage-cache

github.com/wuchangming…

webStorage存储加密

AES 和 RSA

  • 对称加密算法:只需要使用一个密钥可以加密,也可以解密。
  • 非对称加密算法:有私钥和公钥之分,使用公钥加密之后,必须使用私钥才可以解密。

URL

encodeURLComponent + decodeURLComponent

base64

window.btoa + window.atob

注意事项

  1. 如果传入字符串不是有效的 base64 字符串,比如其长度不是 4 的倍数,其是中文时 则抛出DOMException。
// 编码
function utf8_to_b64(str) {
    return window.btoa(unescape(encodeURIComponent(str)));
}

// 解码
function b64_to_utf8(str) {
    return decodeURIComponent(escape(window.atob(str)));
}

复杂加密

Web Crypto API 的SubtleCrypto

html

<button type="button" id="btnEncrypt">加密</button>
<button type="button" id="btnDecrypt">解密</button>

js

<script>

        let publicKey;
        let privateKey;

        let arrayBuffer;
        (async function init() {
            // 生成私钥(privateKey)和公钥(publicKey)
            // 加密用公钥, 解密用私钥
            const keyPair = await crypto.subtle.generateKey(
                {
                    name: "RSA-OAEP",
                    modulusLength: 2048,
                    publicExponent: new Uint8Array([1, 0, 1]),
                    hash: "SHA-256",
                },
                true,
                ["encrypt", "decrypt"]
            );
            publicKey = keyPair.publicKey;
            privateKey = keyPair.privateKey;
        })()

        /**
         * 返回ArrayBuffer
         */
        function encrypt(text, publicKey) {
            // 字符串转为TypedArray
            const clearText = new TextEncoder().encode(text)
            return window.crypto.subtle.encrypt(
                {
                    name: "RSA-OAEP"
                },
                publicKey,
                // an ArrayBuffer, or a TypedArray
                clearText
            );
        }

        /**
         *  cipherText: ArrayBuffer
         * 
        */ 
        async function decrypt(cipherText, privateKey) {
            // cipherText 是ArrayBuffer
            let decrypted = await window.crypto.subtle.decrypt(
                {
                    name: "RSA-OAEP"
                },
                privateKey,
                cipherText
            );
            const dec = new TextDecoder();
            return dec.decode(decrypted);
        }

        btnEncrypt.onclick = async () => {
            const text = textAreaClearText.value;
            arrayBuffer = await encrypt(text, publicKey);

            // ArrayBuffer转为字符串
            const dec = new TextDecoder();
            textAreaCipherText.value = dec.decode(arrayBuffer);
            console.log("arrayBuffer:", arrayBuffer);
        }

        btnDecrypt.onclick = async () => {
            const text = await decrypt(arrayBuffer, privateKey);
            textAreaClearText.value = text
        }

    </script>

加密库

  • crypro-js

www.npmjs.com/package/cry…

第三方库

  • secure-js

www.npmjs.com/package/sec…

webStorage存储空间

采用什么字符编码

The keys and the values stored with localStorage are always in the UTF-16 DOMString format, which uses two bytes per character. As with objects, integer keys are automatically converted to strings.

localStorage 存储的键和值始终采用 UTF-16 DOMString 格式,每个字符使用两个字节。与对象一样,整数键将自动转换为字符串。

答案: UTF-16

存储是多大

 localStorage 的容量限制因浏览器而异。在大多数现代浏览器中,localStorage 的容量限制约为 5MB - 10MB

5MB的单位是什么

  1. 字符的个数
  2. 字节数
  3. 字符的长度值 ✅
  4. bit 数
  5. utf-16编码单元 ✅

一个utf-16的编码单元是 两个字节,所以 是5M的字符的长度,10M的字节

键占不占空间

  • 键名尽量短小

如何检测容量

navigator.storage.estimate()
.then(
  function(storageEstimate) {
    console.log(`可用空间大小:${storageEstimate.quota}`);  
    
    console.log(`已使用空间大小:${storageEstimate.usage}`); })
    
    .catch(function(error) { console.error(error); });

注意点

  • navigator.storage.estimate() 方法只是一个估计值,并不是一个精确的数值。实际上,大多数浏览器都会动态调整可用的存储空间,以适应用户的实际使用情况。因此,在编写代码时应该谨慎处理存储空间的估计值。

  • 如果要存储大量的数据,请考虑使用其他存储方式,例如 IndexedDB 或 Web SQL Database。

应用场景

Local Storage

考虑到 Local Storage 的特点之一是持久,有时我们更倾向于用它来存储一些内容稳定的资源。比如图片内容丰富的电商网站会用它来存储 Base64 格式的图片字符串:

image.png 有的网站还会用它存储一些不经常更新的 CSS、JS 等静态资源。

Session Storage

Session Storage 更适合用来存储生命周期和它同步的会话级别的信息。这些信息只适用于当前会话,当你开启新的会话时,它也需要相应的更新或释放。比如微博的 Session Storage 就主要是存储你本次会话的浏览足迹:

image.png