Skip to content

浏览器本地存储的方式有哪些?区别及应用场景?

浏览器存储

一、方式

javaScript本地缓存的方法我们主要讲述以下四种:

  • cookie
  • sessionStorage
  • localStorage
  • indexedDB

Cookie,类型为「小型文本文件」,指某些网站为了辨别用户身份而储存在用户本地终端上的数据。是为了解决 HTTP 无状态导致的问题

作为一段一般不超过 4KB 的小型文本数据,它由一个名称(Name)、一个值(Value)和其它几个用于控制 cookie 有效期、安全性、使用范围的可选属性组成

但是cookie在每次请求中都会被发送,如果不使用 HTTPS 并对其加密,其保存的信息很容易被窃取,导致安全风险。举个例子,在一些使用 cookie 保持登录态的网站上,如果 cookie 被窃取,他人很容易利用你的 cookie 来假扮成你登录网站

关于cookie常用的属性如下:

  • Expires 用于设置 Cookie 的过期时间
js
Expires=Wed, 21 Oct 2015 07:28:00 GMT
  • Max-Age 用于设置在 Cookie 失效之前需要经过的秒数(优先级比Expires高)
js
Max-Age=604800
  • Domain 指定了 Cookie 可以送达的主机名
  • Path 指定了一个 URL 路径,这个路径必须出现在要请求的资源的路径中才可以发送 Cookie 首部
js
Path=/docs   # /docs/Web/ 下的资源会带 Cookie 首部
  • 标记为 Secure Cookie 只应通过被HTTPS协议加密过的请求发送给服务端

通过上述,我们可以看到cookie又开始的作用并不是为了缓存而设计出来,只是借用了cookie的特性实现缓存

关于cookie的使用如下:

js
document.cookie = '名字=值';

关于cookie的修改,首先要确定domainpath属性都是相同的才可以,其中有一个不同得时候都会创建出一个新的cookie

js
Set-Cookie:name=aa; domain=aa.net; path=/  # 服务端设置
document.cookie =name=bb; domain=aa.net; path=/  # 客户端设置

最后cookie的删除,最常用的方法就是给cookie设置一个过期的事件,这样cookie过期后会被浏览器删除

之所以有第三方 Cookie 这个称呼,是因为 Cookie 执行同源策略,a.comb.com 各自只能访问自己的 Cookie,无法访问对方或者任何不属于自己的 Cookie

如果在访问 a.com 时,设置了一个 b.com 的 Cookie(比如内嵌 b.com 的页面),那么这个 Cookie 相对于 a.com 而言就是第三方 Cookie

值得一提的是,是同一个 host 下的不同端口倒是可以互相访问 Cookie

这里提一下对第三方 Cookie 而言非常重要的一个特性:Cookie 可以被服务端设置

服务器可以通过 response 的请求头来要求浏览器设置 Cookie

shell
Set-Cookie: userId=123;

浏览器在检测到返回请求的 header 里有 Set-Cookie 请求头后,就会自动设置 Cookie,不需要开发者用 JS 去做额外的操作

这样带来的好处是,当 abc.comxyz.com 想在自己的网页上内嵌淘宝广告时,只需要把淘宝提供的组件放进 HTML 即可,不需要写额外的 JS,也能让淘宝进行跨站定位用户

xml
<img src="taobao.com/some-ads" />

(这个组件纯属虚构,仅为方便理解)

它是如何工作的呢?

  1. 当用户处于 abc.com 时,浏览器会向 taobao.com/some-ads 发起一个 HTTP 请求
  2. 当淘宝服务器返回广告内容时,会顺带一个 Set-Cookie 的 HTTP 请求头,告诉浏览器设置一个源为 taobao.com 的 Cookie,里面存上当前用户的 ID 等信息
  3. 这个 Cookie 相对于 abc.com 而言就是第三方 Cookie,因为它属于 taobao.com
  4. 而当用户访问 xyz.com 时,由于 xyz.com 上也嵌入了淘宝的广告,因此用户的浏览器也会向 taobao.com/some-ads 发起请求
  5. 有意思的来了,发请求时,浏览器发现本地已有 taobao.com 的 Cookie(此前访问 abc.com 时设置的),因此,浏览器会将这个 Cookie 发送过去
  6. 淘宝服务器根据发过来的 Cookie,发现当前访问 xyz.com 的用户和之前访问 abc.com 的用户是同一个,因此会返回相同的广告

广告商用第三方 Cookie 来跨站定位用户大概就是这么个过程,实际肯定要复杂许多,但基本原理是一致的

总之,关键就是利用了 Cookie 的两个特点

  • Cookie 可以被服务器设置
  • 浏览器每次请求会自动带上 Cookie

正因为这两个特点,即使 Cookie 在今天看来缺点一大堆,但仍然在部分领域有不可替代的价值

但也是因为这两个特点,导致 Cookie 的安全性相对不高,总之 Cookie 的这个设计放在今天来看,就是一把双刃剑

localStorage

HTML5新方法,IE8及以上浏览器都兼容

特点

  • 生命周期:持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的
  • 存储的信息在同一域中是共享的
  • 当本页操作(新增、修改、删除)了localStorage的时候,本页面不会触发storage事件,但是别的页面会触发storage事件。
  • 大小:5M(跟浏览器厂商有关系)
  • localStorage本质上是对字符串的读取,如果存储内容多的话会消耗内存空间,会导致页面变卡
  • 受同源策略的限制

下面再看看关于localStorage的使用

设置

js
localStorage.setItem('username','cfangxu');

获取

js
localStorage.getItem('username')

获取键名

js
localStorage.key(0) //获取第一个键名

删除

js
localStorage.removeItem('username')

一次性清除所有存储

js
localStorage.clear()

localStorage 也不是完美的,它有两个缺点:

  • 无法像Cookie一样设置过期时间
  • 只能存入字符串,无法直接存对象
js
localStorage.setItem('key', {name: 'value'});
console.log(localStorage.getItem('key')); // '[object, Object]'

sessionStorage

sessionStorage localStorage 使用方法基本一致,唯一不同的是生命周期,一旦页面(会话)关闭,sessionStorage 将会删除数据

扩展的前端存储方式

indexedDB 是一种低级API,用于客户端存储大量结构化数据(包括, 文件/ blobs)。该API使用索引来实现对该数据的高性能搜索

虽然 Web Storage 对于存储较少量的数据很有用,但对于存储更大量的结构化数据来说,这种方法不太有用。IndexedDB提供了一个解决方案

优点:

  • 储存量理论上没有上限
  • 所有操作都是异步的,相比 LocalStorage 同步操作性能更高,尤其是数据量较大时
  • 原生支持储存JS的对象
  • 是个正经的数据库,意味着数据库能干的事它都能干

缺点:

  • 操作非常繁琐
  • 本身有一定门槛

关于indexedDB的使用基本使用步骤如下:

  • 打开数据库并且开始一个事务

  • 创建一个 object store

  • 构建一个请求来执行一些数据库操作,像增加或提取数据等。

  • 通过监听正确类型的 DOM 事件以等待操作完成。

  • 在操作结果上进行一些操作(可以在 request 对象中找到)

关于使用indexdb的使用会比较繁琐,大家可以通过使用Godb.js库进行缓存,最大化的降低操作难度

二、区别

关于cookiesessionStoragelocalStorage三者的区别主要如下:

  • 存储大小: cookie数据大小不能超过4ksessionStoragelocalStorage 虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大

  • 有效时间:localStorage 存储持久数据,浏览器关闭后数据不丢失除非主动删除数据; sessionStorage 数据在当前浏览器窗口关闭后自动删除;cookie设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭

  • 数据与服务器之间的交互方式, cookie的数据会自动的传递到服务器,服务器端也可以写cookie到客户端; sessionStoragelocalStorage不会自动把数据发给服务器,仅在本地保存

三、应用场景

在了解了上述的前端的缓存方式后,我们可以看看针对不对场景的使用选择:

  • 标记用户与跟踪用户行为的情况,推荐使用cookie
  • 适合长期保存在本地的数据(令牌),推荐使用localStorage
  • 敏感账号一次性登录,推荐使用sessionStorage
  • 存储大量数据的情况、在线文档(富文本编辑器)保存编辑历史的情况,推荐使用indexedDB

扩展

IndexedDB

IndexedDB 的全称是 Indexed Database,从名字中就可以看出,它是一个数据库

IndexedDB 早在 2009 年就有了第一次提案,但其实它和 Web Storage 几乎是同一时间普及到各大浏览器的(没错,就是 2015 年那会,es6 也是那时候)

IndexedDB 是一个正经的数据库,它在问世后替代了原来不正经的 Web SQL 方案,成为了当今唯一运行在浏览器里的数据库

在我看来,IndexedDB 其实更适合当作终极前端本地数据储存方案

相比于 LocalStorage,IndexedDB 的优点

  • 储存量理论上没有上限

    • Chrome 对 IndexedDB 储存空间限制的定义是:硬盘可用空间的三分之一
  • 所有操作都是异步的,相比 LocalStorage 同步操作性能更高,尤其是数据量较大时

  • 原生支持储存 JS 的对象

  • 是个正经的数据库,意味着数据库能干的事它都能干

但是缺点也比较致命:

  • 操作非常繁琐
  • 本身有一定门槛(需要你懂数据库的概念)

由于提案较早,IndexedDB 的 API 设计其实是比较糟糕的,对于新手而言,光是想连上数据库,并往里面加东西,都需要折腾半天

对于简单的数据储存而言,IndexedDB 的 API 显得太复杂了,再加上其 API 全是异步的,会带来额外的心智负担,远没有 LocalStorage 简单两行代码搞定数据存取来的快

因此,IndexedDB 在今天的使用规模相比 LocalStorage 差远了,即使 IndexedDB 本身的设计其实更适合用来在浏览器上储存数据

总之,如果不考虑 IndexedDB 的操作难度,其作为一个前端本地储存方案其实是接近完美的

简单理解数据库

在使用 IndexedDB 前,你首先需要懂基本的数据库概念

这里用 Excel 类比,简单介绍数据库的基本概念,不做太深入的讨论

需要了解四个基本概念,以关系型数据库为例

  • 数据库 Database
  • 数据表 Table(IndexedDB 中叫 ObjectStore)
  • 字段 Field
  • 事务 Transaction

(虽然 IndexedDB 算不上关系型数据库,但概念都是相通的)

假设清华和北大各自需要建一个数据库,用来存各自学生与教工的信息,假设命名为

  • 清华:thu
  • 北大:pku

这样,清北之间的数据就可以相互独立

然后,我们再到数据库里建表

  • student 表,储存学生信息
  • stuff 表,储存教工信息

数据表(Table)是什么?说白了,就是一个类似于 Excel 表一样的东西

比如 student 表,可以长这样:

图片image-20210204032958192

上面的 学号、姓名、年龄、专业 就是数据表的字段

当我们想往 student 表添加数据时,就需要按照规定的格式,往表里加数据(关系型数据库的特点,而 IndexedDB 允许不遵守格式)

数据库也给我们提供了方法,当我们知道一个学生的学号(id),就可以在非常短的时间内,在表里成千上万个学生中,快速找到这个学生,并返回他的完整信息

也可以根据 id 定位,对该学生的数据进行修改,或者删除

id 这种每条数据唯一的值,就可以被用来做主键(primary key),主键在表内独一无二,无法添加相同主键的数据

而主键一般会被建立索引,所谓对字段建立索引,就是可以根据这个字段的值,在表里非常快速的找到对应的数据(通常不高于 O(logN)),如果没有索引,那可能就需要遍历整个表(O(N))

增、删、改、查这些操作,都需要通过事务 Transaction 来完成

  • 如果事务中任何一个操作没有成功,整个事务都会回滚
  • 在事务完成之前,操作不会影响数据库
  • 不同事务之间不能互相影响

举个例子,当你发起一个事务,想利用这个事务添加两个学生,如果第一个学生添加成功,但是第二个学生添加失败,事务就会回滚,第一个学生将根本不会在数据库中出现过

事务在银行转账这种场景非常有用:如果转账中任何一步失败了,整个转账操作就和没发生过一样,不会造成任何影响

在同一个 Excel 文件(数据库)中,我们除了 student 表,还可以有 stuff 表(同一个数据库中有了两个不同的数据表):

图片image-20210204033839030

然后,清华和北大各自分一个 Excel 文件,就相当于分了两个数据库

图片image-20210204034441432

总而言之,不扯数据库各种难理解的概念,我们其实完全可以用 Excel 来类比数据库

  • 一个 Excel 文件就是一个 Database
  • 一个 Excel(Database)里可以有很多不同表格(数据表 Table)
  • 表格的列的名称其实就是字段

上述类比最接近 MySQL 这种关系型数据库,但放在其它一些比较特殊的数据库上可能就不太妥当(比如图数据库)

如果你是新手,用 Excel 类比理解数据库完全没问题,足以使用 IndexedDB 了

虽然说 IndexedDB 使用 key-value 的模式储存数据,但你也完全可以用数据表 Table 的模式来看待它

IndexedDB 的使用

使用 IndexedDB 的第一步是打开数据库:

js
const request = window.indexedDB.open('pku');

上面这个操作打开了名为 pku 的数据库,如果不存在,浏览器会自动创建

然后 request 上有三个事件:

js
var db; // 全局 IndexedDB 数据库实例

request.onupgradeneeded = function (event) {
  db = event.target.result;
  console.log('version change');
};

request.onsuccess = function (event) {
  db = request.result;
  console.log('db connected')l;
};

request.onblocked = function (event) {
  console.log('db request blocked!')
}

request.onerror = function (event) {
  console.log('error!');
};

IndexedDB 有一个版本(version)的概念,连接数据库时就可以指定版本

js
const version = 1;
const request = window.indexedDB.open('pku', version);

版本主要用来控制数据库的结构,当数据库结构(表结构)发生变化时,版本也会变化

如上,request 上有四个事件:

  • onupgradeneeded 在版本改变时触发

    • 注意首次连接数据库时,版本从 0 变成 1,因此也会触发,且先于 onsuccess
  • onsuccess 在连接成功后触发

  • onerror 在连接失败时触发

  • onblocked 在连接被阻止的时候触发,比如打开版本低于当前存在的版本

注意这四个事件都是异步的,意味着在连接 IndexedDB 的请求发出去后,需要过一段时间才能连上数据库,并进行操作

开发者对数据库的所有操作,都得放在异步连上数据库之后,这有的时候会带来很大的不便

而开发者如果想创建数据表(在 IndexedDB 里面叫做 ObjectStore),只能将其放到 onupgradeneeded 事件中(官方的定义是需要一个 IDBVersionChange 的事件)

js
request.onupgradeneeded = function (event) {
    db = event.target.result;
    if (!db.objectStoreNames.contains('student')) {
        db.createObjectStore('student', {
            keyPath: 'id', // 主键
            autoIncrement: true // 自增
        });
    }
}

上面这段代码,在数据库初始化时,创建了一个 student 的表,并且以 id 为自增主键(每加一条数据,主键会自动增长,无需开发者指定)

在这一切做好以后,终于,我们可以连接数据库,然后添加数据了

js
const adding = db.transaction('student', 'readwrite') // 创建事务
  .objectStore('student') // 指定 student 表
  .add({ name: 'luke', age: 22 });

adding.onsuccess = function (event) {
  console.log('write success');
};

adding.onerror = function (event) {
  console.log('write failed');
}

用同样的方法再加一条数据

js
db.transaction('student', 'readwrite')
  .objectStore('student')
  .add({ name: 'elaine', age: 23 });

然后,打开浏览器的开发者工具,我们就能看到添加的数据:图片

这里可以看到 IndexedDB 的 key-value 储存特性,key 就是主键(这里指定主键为 id),value 就是剩下的字段和对应的数据

这个 key-value 结构对应的 Table 结构如下:

图片image-20210204050515354

如果要获取数据,需要一个 readonly 的 Transaction

js
const request = db.transaction('student', 'readonly')
  .objectStore(this.name)
  .get(2); // 获取 id 为 2 的数据

request.onsuccess = function (event) {
  console.log(event.target.result) // { id: 2, name: 'elaine', age: 23 }
}

综上,哪怕只是想简单的往 IndexedDB 里增加和查询数据,都需要写一大堆代码,操作非常繁琐,一不小心还容易掉坑里

那么,有没有什么办法,能更优雅的使用 IndexedDB,在代码量减少的情况下,还能更好的发挥其实力呢?

GoDB.js

GoDB.js 是一个基于 IndexedDB 实现前端本地储存的类库

帮你做到代码更简洁的同时,更好的发挥 IndexedDB 的实力

图片

首先安装:

shell
npm install godb

对 IndexedDB 的增删改查,一行代码就可以搞定!

js
import GoDB from 'godb';

const testDB = new GoDB('testDB'); // 连接数据库
const user = testDB.table('user'); // 获取数据表

const data = { name: 'luke', age: 22 }; // 随便定义一个对象

user.add(data) // 增
  .then(luke => user.get(luke.id)) // 查
  .then(luke => user.put({ ...luke, age: 23 })) // 改
  .then(luke => user.delete(luke.id)); // 删

或者,一次性添加许多数据,然后看看效果:

js
const arr = [
    { name: 'luke', age: 22 },
    { name: 'elaine', age: 23 }
];

user.addMany(arr)
  .then(() => user.consoleTable());

上面这段代码,会在添加数据后,在控制台中展示出 user 表的内容:

图片add-many

回到之前 LocalStorage 出问题的那个例子,用 GoDB 就可以实现正常储存:

js
import GoDB from 'godb';

const testDB = new GoDB('testDB'); // 连接数据库
const store = testDB.table('store'); // 获取数据表

const obj = {
    a: undefined,
    b: /abc/,
    c: new Date()
};

store.add(obj)
  .then(item => store.get(item.id)) // 获取存进去的实例
  .then(res => console.log(res));

// {
//     id: 1,
//     a: undefined,
//     b: /abc/,
//     c: new Date()
// }

并且,循环引用的对象也能使用 GoDB 进行储存

js
const a = { key: 'value' };
a['a'] = a;

store.add(a)
  .then(item => store.get(item.id)) // 获取存进去的实例
  .then(result => console.log(result));

// 打印出来的对象比 a 多了个 id,其它完全一致

关于 GoDB 更详细的用法,可以参考 GoDB 的项目官网(不断完善中):

https://godb-js.github.io/

总之,GoDB 可以

  • 帮你在背后处理好 IndexedDB 各种繁琐操作

  • 帮你在背后维护好数据库、数据表和字段

    • 以及字段的索引,各种属性(比如 unique
  • 帮你规范化 IndexedDB 的使用,使你的项目更易维护

  • 最终,开放几个简单易用的 API 给你,让你用简洁的代码玩转 IndexedDB

前端知识体系 · wcrane