深入客户端储存大比拼

Cookie

1.客户端写Cookie

image

注意点:

  • 删除cookie,设置cookie的过期时间为过去时间即可
  • 设置多个cookie,需要多次调用document.cookie
  • 修改和删除cookie字段,要保障path和domian值不变,不然就会失败

2.服务端写Cookie

image

3.会话期cookie

  • 定义:浏览器会话期间的cookie,浏览器关闭以后自动删除
  • 设置会话期cookie:不指定过期时间(Expires)和有效期(Max-Age)即可

4.持久化cookie

  • 定义:持久化cookie的生命周期取决于过期时间(Expires)和有效期(Max-Age)
  • 持久化cookie存储在客户端硬盘中
  • Max-Age:cookie持久化时间(单位秒),Max-Age:0,可以删除cookie

5.httpOnly

  • 定义:设置为true,可以阻止通过 js 访问cookie。能有效的防止 XSS 攻击
  • document.cookie无法访问

6.secure

  • 定义:设置为true,cookie只会在 https 环境下传输到服务端。

7.cookie 作用域

image

cookie 同源和同站区别

  • 同源:协议+端口+域名
  • 同站:有效顶级域名+二级域名,不考虑端口和协议
  • 同站例子:(a.taobao.com/b.taobao.com),(127.0.0.1:8000/127.0.0.1:443)
  • 跨站例子:(a.github.io/b.github.io) ,因为 github.io 属于一个有效的顶级域名

SameSite跨站携带cookie

  • SameSite必须和Secure同时设置才生效
  • 前端站点和后端接口都为https
请求类型 实例 Strict跨站 Lax跨站 None跨站
链接 <a href=”…”/> X
预加载 <link href=”” rel=”prerender”/ X
get表单 <form method=”GET” action=” /> X
post表单 <form method=”POST” action=” /> X X
Iframe <iframe src=””/> X X
AJAX <img src=”” /> X X
Image $.get(“) X X

image

8.cookie key和value编解码

  • 为什么要编解码?
    • key和value中出现分号,等号等特殊符号,影响我们解析操作
    • 写入时需要使用 encodeURIComponent() 编码,
    • 读取时使用 decodeURIComponent() 解码
document.cookie = `${encodeURIComponent("用户名")}=${encodeURIComponent("云牧")}`;

//一般名称使用英文字母,不要用中文,值可以用中文,但是要编码

9.检查用户是否禁用cookie

  • window.navigator.cookieEnabled

10.编写cookie工具库

/**
 *
 * 编码 -方便后续替换编解码方法
 * @param {any} s
 * @returns
 */
function encode(s) {
return encodeURIComponent(s);
}
/**
 *
 * 解码-方便后续替换编解码方法
 * @param {any} s
 * @returns
 */
function decode(s) {
return decodeURIComponent(s);
}

/**
 *
 * 获取cookie
 * @param {any} key
 * @returns
 */
function getCookieItem(key) {
let result = key ? undefined : {},
cookies = document.cookie ? document.cookie.split("; ") : [],
i = 0,
l = cookies.length;
for (; i < l; i++) {
let parts = cookies[i].split("="),
//取第一个等号前面的作为key
name = decode(parts.shift()),
cookie = parts.join("=");

if (key === name) {
result = decode(cookie);
break;
}

if (!key && cookie !== undefined) {
//key 未定义,返回全部的key和value对象
result[name] = decode(cookie);
}
}
return result;
}

/**
 *
 * 设置cookie
 * @param {any} key
 * @param {any} value
 * @param {any} [options={}]
 * @returns
 */
function setCookieItem(key, value, options = {}) {
if (!key) return false;
console.log(options);

let sExpires = "";
if (options.expires) {
switch (options.expires.constructor) {
case Number:
sExpires =
options.expires === Infinity
? "; expires=Fri, 31 Dec 9999 23:59:59 GMT"
: "; max-age=" + options.expires;
break;
case String:
sExpires = "; expires=" + options.expires;
break;
case Date:
sExpires = "; expires=" + options.expires.toUTCString();
break;
}
}

window.document.cookie = [
encode(key),
"=",
encode(value),
sExpires,
options.path ? "; path=" + options.path : "",
options.domain ? "; domain=" + options.domain : "",
options.secure ? "; secure" : "",
].join("");
return true;
}

/**
 *
 * 移除单个cookie字段
 * @param {any} key
 * @param {any} options
 * @returns
 */
function removeCookieItem(key, options) {
setCookieItem(key, "", { ...options, expires: -1 });
return !getCookieItem(key);
}

11.新异步操作CookieAPl-CookieStore

image

image

cookieStore例子:

<body>
  <div>
    <button id="btnAdd">添加</button>
    <button id="btnAddFail">添加失败</button>
    <button id="btnDel">删除</button>
    <button id="btnGet">获取</button>
    <button id="btnGetAll">获取全部</button>
  </div>
  <script>
    let index = 1;
    btnAdd.onclick = function () {
      const day = 24 * 60 * 60 * 1000;
      cookieStore
        .set({
        name: `cookie-${index}`,
        value: `cookie-${index}-value`,
        expires: Date.now() + day,
      })
        .then(
        function () {
          console.log("add cookie success");
        },
        function (reason) {
          console.error("add cookie failed: ", reason);
        }
      );
      index++;
    };
    btnAddFail.onclick = function () {
      const day = 24 * 60 * 60 * 1000;
      cookieStore
        .set({
        name: `cookie-1`,
        value: `cookie-1-value`,
        expires: Date.now() + day,
        domain: "test.com",
      })
        .then(
        function () {
          console.log("add cookie success");
        },
        function (reason) {
          console.error("add cookie failed: ", reason);
        }
      );
    };

    btnDel.onclick = function () {
      cookieStore.delete("cookie-1").then(
        function () {
          console.log("delete cookie success");
        },
        function (reason) {
          console.error("delete cookie failed: ", reason);
        }
      );
    };

    btnGet.onclick = function () {
      cookieStore.get({ name: "cookie-1" }).then(
        function (res) {
          console.log("get cookie success:", res);
        },
        function (reason) {
          console.error("get cookie failed: ", reason);
        }
      );
    };

    btnGetAll.onclick = function () {
      cookieStore.getAll().then(
        function (res) {
          console.log("getAll cookie success:", res);
        },
        function (reason) {
          console.error("getAll cookie failed: ", reason);
        }
      );
    };

    cookieStore.addEventListener("change", function (ev) {
      // ev.changed 变化的
      // ev.deleted 删除的
      console.log("cookieStorage change:", ev.changed, ev.deleted);
    });
  </script>
</body>

CookieStore – service worker中使用

<body>
  <div>
    <button id="btnAdd">添加或更改</button>
    <button id="btnDel">删除</button>
  </div>
  <script>
    let index = 1;
    btnAdd.onclick = function () {
      const day = 24 * 60 * 60 * 1000;
      cookieStore
        .set({
        name: `cookie-x`,
        value: `cookie-x-${index}`,
        expires: Date.now() + day,
      })
        .then(
        function () {
          console.log("add cookie success");
        },
        function (reason) {
          console.error("add cookie failed: ", reason);
        }
      );
      index++;
    };
    btnDel.onclick = function () {
      cookieStore.delete("cookie-x").then(
        function () {
          console.log("delete cookie success");
        },
        function (reason) {
          console.error("delete cookie failed: ", reason);
        }
      );
    };
  </script>
  <script>
    navigator.serviceWorker
      .register("./sw.js", {})
      .then(function (registration) {
      registration.addEventListener("updatefound", function () {
        var installingWorker = registration.installing;
        console.log(
          "A new service worker is being installed:",
          installingWorker
        );
      });
    })
      .catch(function (error) {
      console.log("Service worker registration failed:", error);
    });
  </script>
</body>

sw.js

self.addEventListener("install", async (event) => {
console.log("service worker is installed");
const subscriptions = await self.registration.cookies.getSubscriptions();
await self.registration.cookies.unsubscribe(subscriptions);

await self.registration.cookies.subscribe([
{
name: "cookie-x",
},
]);
});

// 监听变化
self.addEventListener("cookiechange", (ev) => {
console.log("service worker cookiechange:", ev.changed, ev.deleted);
});

console.log("service worker !!!!");

注意事项

  • 安全上下文中可用,比如https,localhost等
  • 返回的都是Promise
  • Firefox 和 Safari 不支持

WebStorge

sessionStorage VS localStorage

  • sessionStorage 为每一个给定的源维持一个独立的存储区域,该存储区域在页面会话期间可用(即只要浏览器处于打开状态,包括页面重新加载和恢复)
  • localStorage 同样的功能,但是在浏览器关闭,然后重新打开后数据仍然存在
  • session Storage和localStorage一般统称为WebStorage

相同点

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

注意事项

  • 是同步APl,阻塞,如果存单个键或者值太大,影响体验
  • 存储的是字符串,要保存对象的时候,需要转为字符串,通常使用JSON.stringify
<body>
  <button type="button" id="btnAdd">添加</button>
  <button type="button" id="btnGet">取值</button>
  <script>
    const str = Array.from({ length: 5 * 1024 * 1023 }, (_) => "云牧").join("");
    const arr = Array.from({ length: 5.12 * 1024 * 33 }, (v, index) => ({
name: "name" + index,
age: ~~(Math.random() * 100),
}));
    btnAdd.onclick = function () {
      console.time(`存长度${str.length}`);
      // localStorage.setItem("_", str);
      localStorage.setItem("_", JSON.stringify(arr));
      console.timeEnd(`存长度${str.length}`);
    };
    btnGet.onclick = function () {
      console.time(`取长度${str.length}`);
      localStorage.getItem("_");
      console.timeEnd(`取长度${str.length}`);
    };
  </script>
</body>

打印结果如下:

image

sessionStroage是共享的吗?

  • 新标签打开一个页面窗口时会复制顶级浏览会话的上下文作为新会话的上下文
  • 打开多个相同的 URL的 Tabs 页面,会创建各自的 session Storage
  • 89版本后,通过a标签target=” blank”跳转到新页面时,sessionStorage就会丢失,但a标签添加属性 rel=”opener” 能够复制,仅仅能复制,之后的更改并不会同步!

StorageEvent

  • 当前页面使用的 storage 被其他页面修改时会触发 StorageEvent 事件
  • 事件在同一个域下的不同页面之间触发,即在A页面注册了 storge 的监听处理,只有在跟A同域名下的B页面操作 storage 对象,A页面才会被触发 storage 事件.B页面本身不会触发事件。

注意:sessionStorage 能触发 StorageEvent 事件吗?

  • a标签打开:不触发
  • irame嵌套:触发
window.addEventListener("storage", function (e) {
  // 通过e.storageArea分辨是否是sessionStorage触发的storage事件
  if (e.storageArea === sessionStorage) {
    
  }
});

localStorage支持过期

简单的实现

  • 添加一个属性,记住过期的时间
  • 添加数据的时候,一起保存
  • 查询数据,比对事件,过期删除
<div>
  <button type="button" id="btnSetItem">添加</button>
  <button type="button" id="btnGetItem">查询</button>
</div>

<body>
  <script>
    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;
        }
      },
    };
  </script>

  <script>
    btnSetItem.onclick = function () {
      myLocalStore.setItem("key-x", "value-1", Date.now() + 10000);
    };
    btnGetItem.onclick = function () {
      console.log("getItem:", myLocalStore.getItem("key-x"));
    };
  </script>
</body>

第三方库

  • web-storage-cache

image

localStorage存储加密

简单加密

  • URL方式:encodeURIComponent + decodeURIComponent
  • base64:window.btoa + window.atob

复杂加密

  • Web Crypto API的 SubtleCrypto 接口提供了许多底层加密功能。
<style>
  * {
    font-size: 28px;
  }

  .flex {
    display: flex;
  }

  .side {
    flex: 0 0 50%;
  }

  textarea {
    min-height: 400px;
    min-width: 400px;
  }
</style>
</head>

<body>
  <div class="flex">
    <div class="side">
      <div>加密前内容</div>
      <div>
        <textarea id="textAreaClearText"></textarea>
      </div>
    </div>
    <div>
      <div>加密后内容</div>
      <div>
        <textarea id="textAreaCipherText"></textarea>
      </div>
    </div>
  </div>

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

  <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>
</body>

使用加密库

  • crypto-js
  • secure-Is
  • localstorage-slim

webStorage的存储空间

  • localStorage 存储的键值采用什么字符编码?
  • 答案:UTF-16
  • UTF-16,每个字符使用两个字节,是有前提条件的,就是码点小于OXFFFF(65535),大于这个码点的是四个字节

5M单位是什么

有几个选项

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

答案是3和5

字符的个数并不等于字符的长度

2个字节作为一个 utf-16 的字符编码单元,也可以说是 5M 的 utf-16 编码单元

image

注意:localStorage 键占存储空间

<body>
  <button type="button" id="btnSave">保存</button>
  <script>
    btnSave.onclick = function () {
      const charTxt = "a";
      let count = 2.5 * 1024 * 1024;
      let content = Array.from({ length: count }, (_) => charTxt).join("");
      const key = Array.from({ length: count }, (_) => charTxt).join("");
      localStorage.clear();
      try {
        console.time("setItem");
        // 此时如果再多设置一位字符就会报错
        localStorage.setItem(key, content);
        console.timeEnd("setItem");
      } catch (err) {
        console.log("err code:", err.code);
        console.log("err message:", err.message);
      }
    };
  </script>
</body>

indexedDB

  • 一个事务型数据库系统
    • 保持操作同步的成功,比如多窗口操作时数据保持同步
  • 一个基于 JavaScript 的面向对象数据库
  • 支持索引
  • 可以存储结构化克隆算法支持的任何对象

不能被结构化克隆算法复制的数据

  • Error 以及 Function 对象
  • DOM 节点
  • 属性描述符, setters 以及 getters
  • 原形链上的属性

indexedDB特点

  • 遵循同源策略
  • 存储空间,配额很大
  • 支持直接存储二进制内容

image

基本操作流程

  1. 打开数据库
  2. 在数据库中创建/对开一个对象仓库
  3. 启动一个事务,并发送一个请求来执行一些数据库操作,像增加或提取数据等
  4. 通过监听 事件以等待操作完成
  5. 继续后续的操作
<body>
  <div>
    <button id="btnAdd">添加数据</button>
    <button id="btnQuery">查询数据</button>
  </div>
  <script>
    // https://github.com/mdn/indexeddb-examples/blob/master/idbkeyrange/scripts/main.js
    var db;
    var things = [
      { fThing: "Drum kit", fRating: 10 },
      { fThing: "Family", fRating: 10 },
      { fThing: "Batman", fRating: 9 },
      { fThing: "Brass eye", fRating: 9 },
      { fThing: "The web", fRating: 9 },
      { fThing: "Mozilla", fRating: 9 },
      { fThing: "Firefox OS", fRating: 9 },
      { fThing: "Curry", fRating: 9 },
      { fThing: "Paneer cheese", fRating: 8 },
      { fThing: "Mexican food", fRating: 8 },
      { fThing: "Chocolate", fRating: 7 },
      { fThing: "Heavy metal", fRating: 10 },
      { fThing: "Monty Python", fRating: 8 },
      { fThing: "Aphex Twin", fRating: 8 },
      { fThing: "Gaming", fRating: 7 },
      { fThing: "Frank Zappa", fRating: 9 },
      { fThing: "Open minds", fRating: 10 },
      { fThing: "Hugs", fRating: 9 },
      { fThing: "Ale", fRating: 9 },
      { fThing: "Christmas", fRating: 8 },
    ];

    // 插入数据
    function insertData() {
      // 事务
      var transaction = db.transaction(["fThings"], "readwrite");
      // 对象库
      var objectStore = transaction.objectStore("fThings");
      // 添加数据
      for (i = 0; i < things.length; i++) {
        var request = objectStore.put(things[i]);
      }
      // 成功的回调
      transaction.oncomplete = function () {
        console.log("insert data success");
      };
    }

    // 我们先打开一个数据库, window.indexedDB(IDBFactory)
    const openRequest = window.indexedDB.open("fThings", 1);
    // 升级的时候创建对象库和对应的索引
    openRequest.onupgradeneeded = function (event) {
      var db = event.target.result;
      db.onerror = function (event) {
        console.log("Error loading database");
      };
      var objectStore = db.createObjectStore("fThings", {
        keyPath: "fThing",
      });
      objectStore.createIndex("fRating", "fRating", { unique: false });
    };

    openRequest.onerror = function (event) {
      console.log("open error:", event);
    };

    openRequest.onsuccess = function (event) {
      console.log("open success");
      // 获得database
      db = event.target.result;
    };

    btnQuery.onclick = function () {
      // 事务
      const transaction = db.transaction(["fThings"], "readonly");
      // 对象库
      const objectStore = transaction.objectStore("fThings");
      // 键
      const keyRangeValue = IDBKeyRange.bound("A", "F");
      // 游标
      objectStore.openCursor(keyRangeValue).onsuccess = function (event) {
        var cursor = event.target.result;
        if (cursor) {
          console.log("value:", cursor.value);
          cursor.continue();
        } else {
          console.log("Entries all displayed.");
        }
      };
    };

    btnAdd.onclick = insertData;
  </script>
</body>

使用场景

  • 缓存数据,比如游戏数据
  • 缓存图片,脚本,json文件等等静态资源
  • service worker的第三方库,就有利用到indexedDB

第三方库

localForage(常用)

  • localForage 是一个 Java Script 库,通过简单类似 localStorageAPI 的异步存储来改进你的 Web 应用程序的离线体验。它能存储多种类型的数据,而不仅仅是字符串
  • localForage 有一个优雅降级策略,若浏览器不支持 IndexedDB 或 WebSQL,则使用 localStorage
  • localForage 提供回调 API 同时也支持 ES6 Promises API 自行选择
<script src="./localforage.min.js"></script>
<script>
  localforage.config({
    // 指定存储媒介, localStorage, web sql, localStorage
    driver: localforage.INDEXEDDB,
    // 数据库名
    name: "myApp",
    // 版本
    version: 1.0,
    // 配额
    size: 4980736,
    // 对象库名
    storeName: "keyvaluepairs",
    description: "some description",
  });

  // 实例1
  var store = localforage.createInstance({
    name: "nameHere",
  });

  // 实例2
  var store2 = localforage.createInstance({
    name: "otherName",
  });

  (async function () {
    // 实例1存和取
    await store.setItem("key-1", "value-1", console.log);
    const storeValue1 = await store.getItem("key-1");
    console.log("store key-1:", storeValue1);
    // 实例2存和取
    await store2.setItem("key-1", "value-1", console.log);
    const store2Value1 = await store.getItem("key-1");
    console.log("store2 key-1:", storeValue1);

    // 纯二进制
    store.setItem("key-2", new Blob(["sdsd"]));
  })();
</script>

dexie.js

  • 解决了原生 indexdb API 的三个主要问题
    • 不明确的错误处理
    • 糟糕的查询
    • 代码复杂性
<script type="module">
  import Dexie from "./dexie.min.mjs";
  // 实例化
  const db = new Dexie("FriendDatabase");
  db.version(1).stores({
    friends: "++id,name,age",
  });

  try {
    // 添加
    await db.friends.add({ name: "Josephine", age: 21 });
    // 查询
    const youngFriends = await db.friends.where("age").below(25).toArray();
    console.log(`My young friends: ${JSON.stringify(youngFriends)}`);
  } catch (e) {
    console.log(`Error: ${e}`);
  }
</script>

ZangoDBs

  • 一个类 MongoDB 的 IndexedDB 接口实现,提供了诸如过滤、投影、排序、更新和聚合等大多数 MongoDB 常见的特性
<script src="./zangodb.min.js"></script>
<script>
  let db = new zango.Db('mydb', { people: ['age'] });
  let people = db.collection('people');

  let docs = [
    { name: 'Frank', age: 20 },
    { name: 'Thomas', age: 33 },
    { name: 'Todd', age: 33 },
    { name: 'John', age: 28 },
    { name: 'Peter', age: 33 },
    { name: 'George', age: 28 }
  ];

  people.insert(docs).then(() => {
    return people.find({
      name: { $ne: 'John' },
      age: { $gt: 20 }
    }).group({
      _id: { age: '$age' },
      count: { $sum: 1 }
    }).project({
      _id: 0,
      age: '$_id.age'
    }).sort({
      age: -1
    }).forEach(doc => console.log('doc:', doc));
  }).catch(error => console.error(error));
</script>

JsStore

  • 一个具备类 SQL 语法的简单和先进的 IndexedDB 封装实现。
<script src="./jsstore.min.js"></script>
<script src="./jsstore.worker.js"></script>
<script>
  // 定义数据结构
  function getDbSchema() {
    var table = {
      name: "Student",
      columns: {
        id: {
          primaryKey: true,
          autoIncrement: true,
        },
        name: {
          notNull: true,
          dataType: "string",
        },
        gender: {
          dataType: "number",
          default: "male",
        },
        country: {
          notNull: true,
          dataType: "string",
        },
        city: {
          dataType: "string",
          notNull: true,
        },
      },
    };

    var db = {
      name: "My-Db",
      tables: [table],
    };
    return db;
  }
  // 实例化
  var jsstoreCon = new JsStore.Connection();

  async function initDb() {
    // 创建数据库
    var isDbCreated = await jsstoreCon.initDb(getDbSchema());
    if (isDbCreated) {
      console.log("db created");
    } else {
      console.log("db opened");
    }
    // 添加
    await addStudent();
    // 查询
    var students = await jsstoreCon.select({
      from: "Student",
    });

    console.log("students", students);
  }

  async function addStudent() {
    try {
      var noOfDataInserted = await jsstoreCon.insert({
        into: "Student",
        values: [
          {
            name: "小名",
            gender: 1,
            country: "北京",
            city: "北京",
          },
          {
            name: "小红",
            gender: 0,
            country: "新疆",
            city: "乌鲁木齐",
          },
        ],
      });
    } catch (ex) {
      console.log("addStudent error:", ex.message);
    }
  }

  initDb();
</script>

客户端存储空间大比拼

准备好一个工具函数查询当前存储字节

const sizeofUtfBytes = function (str, charset) {
var total = 0,
charCode,
i,
len;
charset = charset ? charset.toLowerCase() : "";
if (charset === "utf-16" || charset === "utf16") {
for (i = 0, len = str.length; i < len; i++) {
charCode = str.charCodeAt(i);
if (charCode <= 0xffff) {
total += 2;
} else {
total += 4;
}
}
} else {
for (i = 0, len = str.length; i < len; i++) {
charCode = str.charCodeAt(i);
if (charCode <= 0x007f) {
total += 1;
} else if (charCode <= 0x07ff) {
total += 2;
} else if (charCode <= 0xffff) {
total += 3;
} else {
total += 4;
}
}
}
return total;
};

Cookie

image

最新版浏览器100个左右的存储数量也完全不在话下

<body>
  cookie存储个数
  <script>
    // utf-8 a:一个字节, 人:三个字节
    const charTxt = "a";
    let count = 4 * 1024 - 6;
    console.log(count);
    let content = new Array(parseInt(count)).fill(charTxt).join("");
    const key = "人";

    for (let i = 0; i < 100; i++) {
      try {
        console.log("key==", `${key}_${i}`);
        document.cookie = `${key}_${i}=${content}; path=/`;
      } catch (err) {
        console.log("err", err);
      }
    }
  </script>
</body>

localStorage

image

<button id="lBtn">localStorage 存储</button>
<script>
  lBtn.onclick = function () {
    const charTxt = "人";
    let count = (10 * 1024 * 1024) / 2 - 8 / 2;
    let content = new Array(count).fill(charTxt).join("");
    const key = "aa🔴";
    const key1 = "bb🔴";
    localStorage.clear();
    try {
      localStorage.setItem(key, content);
      //测试存储是否与数量有关。
      localStorage.setItem(key1, content);
    } catch (err) {
      // err.code为22
      console.log("err", err.code, err);
    }
    const sizeKey = sizeofUtfBytes(key, "utf16");
    const contentSize = sizeofUtfBytes(content, "utf16");
    console.log("key size:", sizeKey, content.length);
    console.log("content size:", contentSize, content.length);
    console.log(
      "total size:",
      sizeKey + contentSize,
      content.length + key.length
    );
  };
</script>

sessionStorage

image

indexdDB

image

Cache API 与 Service worker

image

FileSystem(非标准)

image

<body>
  <button id="saveFile">存储文件</button>
  <script>
    //1.获取fileSystem 对象
    window.requestFileSystem =
      window.requestFilsSystem || window.webkitRequestFileSystem;

    saveFile.onclick = function () {
      // 2.申请空间大小
      window.requestFileSystem(
        Window.TEMPORARY,
        10 * 1024 * 1024,
        (fs) => {
          //3.创建文件
          fs.root.getFile(
            "test1.txt",
            { create: true, exclusive: false },
            (fileEntry) => {
              //打印创建好的文件访问URL
              console.log(fileEntry.toURL());
              //4. 创建一个写入对象
              fileEntry.createWriter((fileWriter) => {
                //注册书写成功监听
                fileWriter.onwriteend = function (e) {
                  console.log("书写成功");
                };
                fileWriter.onerror = function (e) {
                  console.log("书写失败 " + e.toString());
                };
                var blob = new Blob(
                  ["测试测试~"],
                  { type: "text/plain" }
                );
                //5. 写入内容
                fileWriter.write(blob);
              });
            },
            (e) => {
              console.log("eee", e);
            }
          );
        },
        (err) => {
          console.log("file error");
        }
      );
    };
  </script>
</body>

总结

特性 cookie localStorage sessionStorage indexedDB
数据声明周期 一般由服务器生成,可设置过期时间 一直存在,需手动清理 浏览器关闭清理 一直存在,需手动清理
数据储存大小 单个Cookie大小4K字节,可存储多个 10M字节( 5M UTF-16编码单元) 10M字节( 5M UTF-16编码单元) 几十G甚至更多
数据获取方式 同步 同步 同步 异步,支持事务
与服务器通讯 会携带在请求header中 不参与 不参与 不参与
作用域 同站 同源 同源(有限定窗口) 同源
存储类型 字符串 字符串 字符串 字符串,二进制
© 版权声明
THE END
喜欢就支持一下吧
点赞13 分享
相关推荐
  • 暂无相关文章
  • 评论 抢沙发
    头像
    欢迎您留下宝贵的见解!
    提交
    头像

    昵称

    取消
    昵称表情代码图片

      暂无评论内容