cookie、localStorage、sessionStorage、IndexedDB 的区别?

现代浏览器中提供了多种存储机制,打开浏览器的控制台(Mac 可以使用 Command + Option + J 快捷键,Windows 可以使用 Control + Shift + J 快捷键)。选择 Application 选项卡,可以在 Storage中 看到 Local Storage、Session Storage、IndexedDB、Web SQL、Cookies 等:

那数据存储在浏览器中有什么使用场景呢?在以下情况下,将数据存储在浏览器中成为更可行的选择:

  • 在浏览器存储中保存应用状态,比如保持用户偏好(用户特定的设置,例如亮模式或暗模式、字体大小等);
  • 创建离线工作的渐进式 Web 应用,除了初始下载和更新之外没有服务器端要求;
  • 缓存静态应用资源,如 HTML、CSS、JS 和图像等;
  • 保存上一个浏览会话中的数据,例如存储上一个会话中的购物车内容,待办事项列表中的项目,记住用户是否以前登录过等。

无论哪种方式,将这些信息保存在客户端可以减少额外且不必要的服务器调用,并帮助提供离线支持。不过,需要注意,由于实现差异,浏览器存储机制在不同浏览器中的行为可能会有所不同。除此之外,许多浏览器已删除对 Web SQL 的支持,建议将现有用法迁移到 IndexedDB。

所以下面我们将介绍 Local Storage、Session Storage、IndexedDB、Cookies 的使用方式、使用场景以及它们之间的区别。

2. Web Storage

HTML5 引入了 Web Storage,这使得在浏览器中存储和检索数据变得更加容易。Web Storage API 为客户端浏览器提供了安全存储和轻松访问键值对的机制。Web Storage 提供了两个 API 来获取和设置纯字符串的键值对:

  • localStorage:用于存储持久数据,除非用户手动将其从浏览器中删除,否则数据将终身存储。即使用户关闭窗口或选项卡,它也不会过期;

  • sessionStorage:用于存储临时会话数据,页面重新加载后仍然存在,关闭浏览器选项卡时数据丢失。

Web Storage API 由 4 个方法 setItem()getItem()removeItem()clear()key()和一个 length 属性组成,以 localStorage 为例:

  • setItem() :用于存储数据,它有两个参数,即keyvalue。使用形式:localStorage.setItem(key, value)
  • getItem():用于检索数据,它接受一个参数 key,即需要访问其值的键。使用形式:localStorage.getItem(key);
  • removeItem():用于删除数据,它接受一个参数 key,即需要删除其值的键。使用形式:localStorage.removeItem(key);
  • clear() :用于清除其中存储的所有数据,使用形式:localStorage.clear();
  • key():该方法用于获取 localStorage 中数据的所有key,它接受一个数字作为参数,该数字可以是 localStorage 项的索引位置。
javascript 复制代码
console.log(typeof window.localStorage) // Object

// 存储数据
localStorage.setItem("colorMode", "dark")
localStorage.setItem("username", "zhangsan")
localStorage.setItem("favColor", "green")

console.log(localStorage.length) // 3

// 检索数据
console.log(localStorage.getItem("colorMode")) // dark

// 移除数据
localStorage.removeItem("colorMode")
console.log(localStorage.length) // 2
console.log(localStorage.getItem("colorMode")) // null

// 检索键名
window.localStorage.key(0); // favColor

// 清空本地存储
localStorage.clear()
console.log(localStorage.length) // 0

localStorage 和 sessionStorage 都非常适合缓存非敏感应用数据。可以在需要存储少量简单值并不经常访问它们是使用它们。它们本质上都是同步的,并且会阻塞主 UI 线程,所以应该谨慎使用。

我们可以在浏览器上监听 localStorage 和 sessionStorage 的存储变化。 storage 事件在创建、删除或更新项目时触发。侦听器函数在事件中传递,具有以下属性:

  • newValue:当在存储中创建或更新项目时传递给 setItem() 的值。 当从存储中删除项目时,此值设置为 null。
  • oldValue:创建新项目时,如果该键存在于存储中,则该项目的先前的值。
  • key:正在更改的项目的键,如果调用 .clear(),则值为 null。
  • url:执行存储操作的 URL。
  • storageArea:执行操作的存储对象(localStorage 或 sessionStorage)。

通常,我们可以使用 window.addEventListener("storage", func) 或使用 onstorage 属性(如 window.onstorage = func)来监听 storage 事件:

ini 复制代码
window.addEventListener('storage', e => {
  console.log(e.key);
  console.log(e.oldValu);
  console.log(e.newValue);
});

window.onstorage = e => {
  console.log(e.key);
  console.log(e.oldValu);
  console.log(e.newValue);
});

注意,该功能不会在发生更改的同一浏览器选项卡上触发,而是由同一域的其他打开的选项卡或窗口触发。此功能用于同步同一域的所有浏览器选项卡/窗口上的数据。 因此,要对此进行测试,需要打开同一域的另一个选项卡。

localStorage 和 sessionStorage 只能存储 5 MB 的数据,因此需要确保存储的数据不会超过此限制。

javascript 复制代码
localStorage.setItem('a', Array(1024 * 1024 * 5).join('a'))
localStorage.setItem('b', 'a')

// Uncaught DOMException: Failed to execute 'setItem' on 'Storage': Setting the value of `a` exceeded the quota.

在上面的例子中,收到了一个错误,首先创建了一个5MB的大字符串,当再添加其他数据时就报错了。

另外,localStorage 和 sessionStorage 只接受字符串。可以通过 JSON.stringifyJSON.parse 来解决这个问题:

css 复制代码
const user = {
  name : "zhangsan",
  age : 28,
  gender : "male",
  profession : "lawyer" 
};

localStorage.setItem("user", JSON.stringify(user));
localStorage.getItem("user");   // '{"name":"zhangsan","age":28,"gender":"male","profession":"lawyer"}'
JSON.parse(localStorage.getItem("user"))  // {name: 'zhangsan', age: 28, gender: 'male', profession: 'lawyer'}

如果我们直接将一个对象存储在 localStorage 中,那将会在存储之前进行隐式类型转换,将对象转换为字符串,再进行存储:

javascript 复制代码
const user = {
  name : "zhangsan",
  age : 28,
  gender : "male",
  profession : "lawyer" 
};

localStorage.setItem("user", user);
localStorage.getItem("user");  // '[object Object]'

Web Storage 使用了同源策略,也就是说,存储的数据只能在同一来源上可用。如果域和子域相同,则可以从不同的选项卡访问 localStorage 数据,而无法访问 sessionStorage 数据,即使它是完全相同的页面。

另外:

  • 无法在 web worker 或 service worker 中访问 Web Storage;
  • 如果浏览器设置为隐私模式,将无法读取到 Web Storage;
  • Web Storage 很容易被 XSS 攻击,敏感信息不应存储在本地存储中;
  • 它是同步的,这意味着所有操作都是一次一个。对于复杂应用,它会减慢应用的运行时间。

Cookie 主要用于身份验证和用户数据持久性。Cookie 与请求一起发送到服务器,并在响应时发送到客户端;因此,cookies 数据在每次请求时都会与服务器交换。服务器可以使用 cookie 数据向用户发送个性化内容。严格来说,cookie 并不是客户端存储方式,因为服务器和浏览器都可以修改数据。它是唯一可以在一段时间后自动使数据过期的方式。

每个 HTTP 请求和响应都会发送 cookie 数据。存储过多的数据会使 HTTP 请求更加冗长,从而使应用比预期更慢:

  • 浏览器限制 cookie 的大小最大为4kb,特定域允许的 cookie 数量为 20 个,并且只能包含字符串;
  • cookie 的操作是同步的;
  • 不能通过 web workers 来访问,但可以通过全局 window 对象访问。

Cookie 通常用于会话管理、个性化以及跨网站跟踪用户行为。我们可以通过服务端和客户端设置和访问 cookie。Cookie 还具有各种属性,这些属性决定了在何处以及如何访问和修改它们,

Cookie 分为两种类型:

  • 会话 Cookie:没有指定 Expires 或 Max-Age 等属性,因此在关闭浏览器时会被删除;

  • 持久性 Cookie:指定 Expires 或 Max-Age 属性。这些 cookie 在关闭浏览器时不会过期,但会在特定日期 (Expires) 或时间长度 (Max-Age) 后过期。

下面先来看看如何访问和操作客户端和服务器上的 cookie。

① 客户端(浏览器)

客户端 JavaScript 可以通过 document.cookie 来读取当前位置可访问的所有 cookie。它提供了一个字符串,其中包含一个以分号分隔的 cookie 列表,使用 key=value 格式。

ini 复制代码
document.cookie;

可以看到,在语雀主页中获取 cookie,结果中包含了登录的 cookie、语言、当前主题等。

同样,可以使用 document.cookie 来设置 cookie 的值,设置cookie也是用key=value格式的字符串,属性用分号隔开:

ini 复制代码
document.cookie = "hello=world; domain=example.com; Secure";

这里用到了两个属性 SameSite 和 Secure,下面会介绍。如果已经存在同名的 cookie 属性,就会更新已有的属性值,如果不存在,就会创建一个新的 key=value。

如果需要经常在客户端处理 Cookie,建议使用像 js-cookie 这样的库来处理客户端 cookie:

csharp 复制代码
Cookies.set('hello', 'world', { domain: 'example.com', secure: true });
Cookies.get('hello'); // -> world

这样不仅为 cookie 上的 CRUD 操作提供了一个干净的 API,而且还支持 TypeScript,从而帮助避免属性的拼写错误。

② 服务端(Node.js)

服务端可以通过 HTTP 请求的请求头和响应头来访问和修改 cookie。每当浏览器向服务端发送 HTTP 请求时,它都会使用 cookie 头将所有相关 cookie 都附加到该站点。请求标头是一个分号分隔的字符串。

这样就可以从请求头中读取这些 cookie。如果在服务端使用 Node.js,可以像下面这样从请求对象中读取它们,将获得以分号分隔的 key=value 对:

ini 复制代码
http.createServer(function (request, response) {
    const cookies = request.headers.cookie;
    // "cookie1=value1; cookie2=value2"
    ...
}).listen(8124);

如果想要设置 cookie,可以在响应头中添加 Set-Cookie 头,其中 cookie 采用 key=value 的格式,属性用分号分隔:

ini 复制代码
response.writeHead(200, {
    'Set-Cookie': 'mycookie=test; domain=example.com; Secure'
});

通常我们不会直接编写 Node.js,而是与 ExpressJS 这样的 Node.js 框架一起使用。使用 Express 可以更轻松地访问和修改 cookie。只需添加一个像 cookie-parser 这样的中间件,就可以通过 req.cookies 以 JavaScript 对象的形式获得所有的 cookie。 还可以使用 Express 内置的 res.cookie() 方法来设置 cookie:

javascript 复制代码
const express = require('express')
const cookieParser = require('cookie-parser')
    
const app = express()
app.use(cookieParser())
    
app.get('/', function (req, res) {
    console.log('Cookies: ', req.cookies)
    // Cookies: { cookie1: 'value1', cookie2: 'value2' }

    res.cookie('name', 'tobi', { domain: 'example.com', secure: true })
})
    
app.listen(8080)

4. IndexedDB

IndexedDB 提供了一个类似 NoSQL 的 key/value 数据库,它可以存储大量结构化数据,甚至是文件和 blob。 每个域至少有 1GB 的可用空间,并且最多可以达到剩余磁盘空间的 60%。

IndexedDB 于 2011 年首次实现,并于 2015 年 1 月成为 W3C 标准,它具有良好的浏览器支持。

key/value 数据库意味着存储的所有数据都必须分配给一个 key。它将key 与 value 相关联,key 用作该值的唯一标识符,这意味着可以使用该 key 跟踪该值。如果应用需要不断获取数据,key/value 数据库使用非常高效且紧凑的索引结构来快速可靠地通过 key 定位值。使用该 key,不仅可以检索存储的值,还可以删除、更新和替换该值。

在说 IndexedDB 之前,先来看一些相关术语:

  • 数据库: 一个域可以创建任意数量的 IndexedDB 数据库,只有同一域内的页面才能访问数据库。
  • object store:相关数据项的 key/value 存储。它类似于 MongoDB 中的集合或关系数据库中的表。
  • key:用于引用 object store ****中每条记录(值)的唯一名称。它可以使用自动增量数字生成,也可以设置为记录中的任何唯一值。
  • index:在 object store 中组织数据的另一种方式。搜索查询只能检查 key 或 index。
  • schema:object store、key 和 index 的定义。
  • version:分配给 schema 的版本号(整数)。 IndexedDB 提供自动版本控制,因此可以将数据库更新到最新 schema。
  • 操作:数据库活动,例如创建、读取、更新或删除记录。

indexedDB 特点如下:

  • 可以将任何 JavaScript 类型的数据存储为键值对,例如对象(blob、文件)或数组等。

  • IndexedDB API 是异步的,不会在数据加载时停止页面的渲染。

  • 可以存储结构化数据,例如 Date、视频、图像对象等。

  • 支持数据库事务和版本控制。

  • 可以存储大量数据。

  • 可以在大量数据中快速定位/搜索数据。

  • 数据库是域专用的,因此任何其他站点都无法访问其他网站的 IndexedDB 存储,这也称为同源策略。

In dexedDB 使用场景:

  • 存储用户生成的内容: 例如表单,在填写表单的过程中,用户可以离开并稍后再回来完成表单,存储之后就不会丢失初始输入的数据。

  • 存储应用状态: 当用户首次加载网站或应用时,可以使用 IndexedDB 存储这些初始状态。可以是登录身份验证、API 请求或呈现 UI 之前所需的任何其他状态。因此,当用户下次访问该站点时,加载速度会增加,因为应用已经存储了状态,这意味着它可以更快地呈现 UI。

  • 对于离线工作的应用: 用户可以在应用离线时编辑和添加数据。当应用程序来连接时,IndexedDB 将处理并清空同步队列中的这些操作。

不同浏览器的 IndexedDB 可能使用不同的名称。可以使用以下方法检查 IndexedDB 支持:

javascript 复制代码
const indexedDB =
  window.indexedDB ||
  window.mozIndexedDB ||
  window.webkitIndexedDB ||
  window.msIndexedDB ||
  window.shimIndexedDB;

if (!indexedDB) {
  console.log("不支持 IndexedDB");
}

可以使用 indexedDB.open() 来连接数据库:

ini 复制代码
const dbOpen = indexedDB.open('performance', 1);

indexedDB.open 的第一个参数是数据库名称,第二个参数是可选的版本整数。

可以使用以下三个事件处理函数监听 indexedDB 的连接状态:

① onerror

在无法建立 IndexedDB 连接时,将触发该事件:

ini 复制代码
// 连接失败
dbOpen.onerror = e => {
  reject(`IndexedDB error: ${ e.target.errorCode }`);
};

如果在无痕模式、隐私模式下运行浏览器,可能不支持 IndexedDB,需要禁用这些模式。

② onupgradeneeded

一旦数据库连接打开,就会触发 onupgradeneeded 事件,该事件可用于创建 object store。

ini 复制代码
dbOpen.onupgradeneeded = e => {
   const db = dbOpen.result;

   // 创建 object store
   const store = db.createObjectStore("cars", { keyPath: "id" });
   // 使用自动递增的id
   // const store = db.createObjectStore('cars', { autoIncrement: true }); 

   // 创建索引
   
   store.createIndex("cars_colour", ["colour"], { 
       unique: true 
   }); 

   // 创建复合索引
   store.createIndex("colour_and_make", ["colour", "make"], {
    unique: false,
  });
};

IndexedDB 使用了 object store 的概念,其本质上是数据集合的名称。可以在单个数据库中创建任意数量的 object store。keyPath是 IndexedDB 将用来识别对象字段名称,通常是一个唯一的编号,也可以通过 autoIncrement: true 来自动为 store 设置唯一递增的 ID。除了普通的索引,还可以创建复合索引,使用多个关键词的组合进行查询。

③ onsuccess

在连接建立并且所有升级都完成时,将触发该事件。上面我们已经新建了 schema,接下来就可以在onsuccess 中添加、查询数据。

ini 复制代码
// 连接成功
dbOpen.onsuccess = () => {
  this.db = dbOpen.result;

  //1
  const transaction = db.transaction("cars", "readwrite");
  
  //2
  const store = transaction.objectStore("cars");
  const colourIndex = store.index("cars_colour");
  const makeModelIndex = store.index("colour_and_make");

  //3
  store.put({ id: 1, colour: "Red", make: "Toyota" });
  store.put({ id: 2, colour: "Red", make: "Kia" });
  store.put({ id: 3, colour: "Blue", make: "Honda" });
  store.put({ id: 4, colour: "Silver", make: "Subaru" });

  //4
  const idQuery = store.get(4);
  const colourQuery = colourIndex.getAll(["Red"]);
  const colourMakeQuery = makeModelIndex.get(["Blue", "Honda"]);

  // 5
  idQuery.onsuccess = function () {
    console.log('idQuery', idQuery.result);
  };
  colourQuery.onsuccess = function () {
    console.log('colourQuery', colourQuery.result);
  };
  colourMakeQuery.onsuccess = function () {
    console.log('colourMakeQuery', colourMakeQuery.result);
  };

  // 6
  transaction.oncomplete = function () {
    db.close();
  };
};

这里总共有六部分:

  1. 为了对数据库执行操作,我们必须创建一个 schema,一个 schema 可以是单个操作,也可以是多个必须全部成功的操作,否则都不会成功;
  2. 这里用来获取 cars object store 的引用以及对应的索引;
  3. object store 上的 put 方法用于将数据添加到数据库中;
  4. 这里就是数据的查询,可以使用 keyPath 的值直接查询项目(第14行);第15行中的 getAll 方法将返回一个包含它找到的每个结果的数组,我们正在根据 cars_colour 索引来搜索 Red,应该会查找到两个结果。第16行根据复合索引查找颜色为Blue,并且品牌为 Honda 的结果。
  5. 搜索成功的事件处理函数,它们将在查询完成时触发。
  6. 最后,在事务完成时关闭与数据库连接。 无需使用 IndexedDB 手动触发事务,它会自行运行。

运行上面的代码,就会得到以下结果:

可以在 Chrome Devtools 中查看:

下面来看看如何更新和删除数据。

  • 更新: 首先使用个 get 来获取需要更新的数据,然后使用 store 上的 put 方法更新现有数据。 put 是一种"插入或更新"方法,它要么覆盖现有数据,要么在新数据不存在时插入新数据。
ini 复制代码
const subaru = store.get(4);

subaru.onsuccess= function () {
  subaru.result.colour = "Green";
  store.put(subaru.result);
}

这会将数据库中 Silver 色的 Subaru 的颜色更新为绿色。

  • 删除:可以使用 delete API 来删除数据,最简单的方法是通过其 key 来删除:
ini 复制代码
const deleteCar = store.delete(1);

deleteCar.onsuccess = function () {
  console.log("Removed");
};

如果不知道 key 并且希望根据值来删除,可以这样:

ini 复制代码
const redCarKey = colourIndex.getKey(["Red"]);

redCarKey.onsuccess = function () {
  const deleteCar = store.delete(redCarKey.result);

  deleteCar.onsuccess = function () {
    console.log("Removed");
  };
};

结果如下:

总结:

cookie 、localStorage 和 sessionStorage:

  • cookie: 其实最开始是服务器端用于记录用户状态的一种方式,由服务器设置,在客户端存储,然后每次发起同源请求时,发送给服务器端。cookie 最多能存储 4 k 数据,它的生存时间由 expires 属性指定,并且 cookie 只能被同源的页面访问共享。

  • sessionStorage: html5 提供的一种浏览器本地存储的方法,它借鉴了服务器端 session 的概念,代表的是一次会话中所保存的数据。它一般能够存储 5M 或者更大的数据,它在当前窗口关闭后就失效了,并且 sessionStorage 只能被同一个窗口的同源页面所访问共享。

  • localStorage: html5 提供的一种浏览器本地存储的方法,它一般也能够存储 5M 或者更大的数据。它和 sessionStorage 不同的是,除非手动删除它,否则它不会失效,并且 localStorage 也只能被同源页面所访问共享。

上面三种方式都是存储少量数据的时候的存储方式,当需要在本地存储大量数据的时候,我们可以使用浏览器的 indexDB 这是浏览器提供的一种本地的数据库存储机制。它不是关系型数据库,它内部采用对象仓库的形式存储数据,它更接近 NoSQL 数据库。

Web Storage 和 cookie 的区别总结如下:

  • Web Storage是为了更大容量存储设计的。Cookie 的大小是受限的,并且每次你请求一个新的页面的时候 Cookie 都会被发送过去,这样无形中浪费了带宽;
  • cookie 需要指定作用域,不可以跨域调用;
  • Web Storage 拥有 setItem,getItem,removeItem,clear 等方法,不像 cookie 需要前端开发者自己封装 setCookie,getCookie;
  • Cookie 也是不可以或缺的:Cookie 的作用是与服务器进行交互,作为 HTTP 规范的一部分而存在 ,而 Web Storage 仅仅是为了在本地"存储"数据而生。
相关推荐
前端郭德纲9 分钟前
React Native 性能调试指南
javascript·react native·react.js
○陈14 分钟前
vue2面试题10|[2024-11-24]
前端·javascript·vue.js
在荒野的梦想19 分钟前
Vue-TreeSelect组件最下级隐藏No sub-options
前端·javascript·vue.js
田本初1 小时前
浏览器缓存与协商缓存
前端·javascript·缓存
前端Hardy6 小时前
HTML&CSS:MacBook Air 3D 动画跃然屏上
前端·javascript·css·3d·html
loey_ln7 小时前
观察者模式和发布订阅模式
javascript·观察者模式·react.js
ZL_5679 小时前
uniapp中使用uni-forms实现表单管理,验证表单
前端·javascript·uni-app
sunly_9 小时前
Flutter:启动屏逻辑处理02:启动页
android·javascript·flutter
EasyNTS9 小时前
H5流媒体播放器EasyPlayer.js网页直播/点播播放器如果H.265视频在播放器上播放不流畅,可以考虑的解决方案
javascript·音视频·h.265
莘薪10 小时前
JQuery -- 第九课
前端·javascript·jquery