本地存储:从 Cookie 到 Web Storage、IndexedDB

978 阅读4分钟

从 Cookie 说起

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

在 Web 开发的早期,人们需解决的一个问题就是状态管理的问题:HTTP 协议是一个无状态协议,服务器接收客户端的请求,返回一个响应,故事到此就结束了,服务器并没有记录下关于客户端的任何信息。那么下次请求的时候,如何让服务器知道“我是我”呢?

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

Cookie 就是一个存储在浏览器里的一个小小的文本文件,它附着在 HTTP 请求上,在浏览器和服务器之间“飞来飞去”。它可以携带用户信息,当服务器检查 Cookie 的时候,便可以获取到客户端的状态。

关于 Cookie 的详细内容,我们可以在 Chrome 的 Application 面板中查看到,Cookie 以键值对的形式存储

Cookie 的劣势

  1. Cookie 存储容量小

大家知道,Cookie 是有体积上限的,它最大只能有 4KB。当 Cookie 超过 4KB 时,它将面临被裁切的命运。这样看来,Cookie 只能用来存取少量的信息。

  1. 过多的 Cookie 会带来巨大的性能浪费

同一个域名下的所有请求,都会携带 Cookie。Cookie 虽然小,请求却可以有很多,随着请求的叠加,这样的不必要的 Cookie 带来的开销将是无法想象的。

随着前端应用复杂度的提高,Cookie 也渐渐演化为了一个“存储多面手”——它不仅仅被用于维持状态,还被塞入了一些乱七八糟的其它信息,被迫承担起了本地存储的“重任”。在没有更好的本地存储解决方案的年代里,Cookie 承载了 4KB 内存所不能承受的压力。

为了弥补 Cookie 的局限性,Web Storage 出现了。

向前一步:Web Storage

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

Local Storage 与 Session Storage 的区别

两者的区别在于 生命周期作用域 不同。

  • 生命周期:Local Storage 是持久化的本地存储,存储在其中的数据是永远不会过期的,使其消失的唯一办法是手动删除;而 Session Storage 是临时性的本地存储,它是会话级别的存储,当会话结束(页面被关闭)时,存储内容也随之被释放。
  • 作用域:Local Storage、Session Storage 和 Cookie 都遵循同源策略。但 Session Storage 特别的一点在于,即便是相同域名下的两个页面,只要它们 不在同一个浏览器窗口中 打开,那么它们的 Session Storage 内容便无法共享。

Web Storage 的特性

  • 存储容量大。Web Storage 根据浏览器的不同,存储容量可以达到 5-10M。
  • 仅位于浏览器端,不与服务端发生通信。

Web Storage 核心 API 使用示例

Web Storage 保存的数据内容和 Cookie 一样,是文本内容,以键值对的形式存在。Local Storage 与 Session Storage 在 API 方面无异,这里我们以 localStorage 为例:

  • 存储数据:setItem()
localStorage.setItem('username', 'codinglin')
  • 读取数据: getItem()
localStorage.getItem('username')
  • 删除某一键名对应的数据: removeItem()
localStorage.removeItem('username')
  • 清空数据记录:clear()
localStorage.clear()

终极形态:IndexedDB

IndexedDB 是一个 运行在浏览器上的非关系型数据库。理论上来说,IndexedDB 是没有存储上限的(一般来说不会小于 250M)。它不仅可以存储字符串,还可以存储二进制数据。

接下来,我们遵循 MDN 推荐的操作模式,通过一个基本的 IndexedDB 使用流程,旨在对 IndexedDB 形成一个感性的认知:

  1. 打开/创建一个 IndexedDB 数据库(当该数据库不存在时,open 方法会直接创建一个名为 xiaoceDB 新数据库)。
// 后面的回调中,我们可以通过 event.target.result 拿到数据库实例
let db
// 参数1位数据库名,参数2为版本号
const request = window.indexedDB.open("xiaoceDB", 1)
// 使用失败时的监听函数
request.onerror = function (event) {
  console.log("无法使用IndexedDB")
};
// 成功
request.onsuccess = function (event) {
  // 此处就可以获取到db实例
  db = event.target.result
  console.log("你打开了IndexedDB")
}
  1. 创建一个 object store(object store 对标数据库中的“表”)。
// onupgradeneeded 事件会在初始化数据库/版本发生更新时被调用,我们在它的监听函数中创建 object store
request.onupgradeneeded = function (event) {
  let objectStore
  // 如果同名表未被创建过,则新建test表
  if (!db.objectStoreNames.contains("test")) {
    objectStore = db.createObjectStore("test", { keyPath: "id" })
  }
}
  1. 构建一个事务来执行一些数据库操作,像增加或提取数据等。
// 创建事务,指定表格名称和读写权限
const transaction = db.transaction(["test"], "readwrite")
// 拿到 Object Store 对象
const objectStore = transaction.objectStore("test")
// 向表格写入数据
objectStore.add({ id: 1, name: "xiuyan" })
  1. 通过监听正确类型的事件以等待操作完成。
// 操作成功时的监听函数
transaction.oncomplete = function (event) {
  console.log("操作成功")
}
// 操作失败时的监听函数
transaction.onerror = function (event) {
  console.log("这里有一个Error")
}