前言
前端的数据存储方式,你除了用过 Cookies、localStorage 和 sessionStorage 外,还有用过其它的存储方式么?其实除了前面提到的 3 种存储方式,目前主流的浏览器还支持 Web SQL 和 IndexedDB。
目前市场上主流的浏览器有 Chrome 、Safari、Firefox、Opera、UC Browser 和 Internet Explorer 等,其中截止 2020 年 5 月,Chrome 的市场占有率为 「63.93%」,远远超过第二名 Safari 浏览(「18.19%」)。
这里我们以市场占有率第一的 Chrome 浏览器为例,来了解一下它支持的所有存储方案:
(打开 Chrome 开发者工具,切换至 Application 栏位)
在介绍目前比较流行的一些开源的前端存储方案之前,阿宝哥先分享一些与存储有关,有趣好玩的开源库。
一、有趣好玩的开源库
1.1 Sharedb
- ❝
- Realtime database backend based on Operational Transformation (OT)。
- https://github.com/share/sharedb
- ❞
ShareDB 是一个基于 JSON 文档操作转换(OT)的实时数据库后端。它是 DerbyJS Web 应用程序框架的实时后端。
「示例1:实时数据同步」
「示例2:展示实时查询的排行榜应用程序」
1.2 ImmortalDB
- ❝
- A relentless key-value store for the browser。
- https://github.com/gruns/ImmortalDB
- ❞
ImmortalDB 是在浏览器中存储持久键值数据的最佳方法。保存到 ImmortalDB 的数据被冗余地存储在 Cookies,IndexedDB 和 localStorage 中,并且如果其中的任何数据被删除或损坏,它们将不断进行自我修复。
例如,清除 Cookie 是一种常见的用户操作,即使对于非技术用户也是如此。在存储压力下,浏览器在没有警告的情况下随意删除 IndexedDB、localStorage 或 sessionStorage。
「示例」
- import { ImmortalDB } from 'immortal-db'
- await ImmortalDB.set('name', 'semlinker'); // Set
- await ImmortalDB.get('name', default='lolo'); // Get
- await ImmortalDB.remove('name'); // Remove
1.3 web-storage-cache
对 localStorage 和 sessionStorage 进行了扩展,添加了超时时间,序列化方法。
https://github.com/wuchangming/web-storage-cache
WebStorageCache 对 HTML5 localStorage 和 sessionStorage 进行了扩展,「添加了超时时间,序列化方法」。可以直接存储 JSON 对象,同时可以非常简单的进行超时时间的设置。
优化:WebStorageCache 自动清除访问的过期数据,避免了过期数据的累积。另外也提供了清除全部过期数据的方法:wsCache.deleteAllExpires();
「示例」
- var wsCache = new WebStorageCache();
- // 缓存字符串'wqteam' 到 'username' 中, 超时时间100秒
- wsCache.set('username', 'wqteam', {exp : 100});
- // 超时截止日期,可用使用Date类型
- var nextYear = new Date();
- nextYear.setFullYear(nextYear.getFullYear() + 1);
- wsCache.set('username', 'wqteam', {exp : nextYear});
- // 获取缓存中 'username' 的值
- wsCache.get('username');
- // 缓存简单js对象,默认使用序列化方法为JSON.stringify。
- // 可以通过初始化wsCache的时候配置serializer.serialize
- wsCache.set('user', { name: 'Wu', organization: 'wqteam'});
1.4 lz-string
LZ-based compression algorithm for JavaScript。
https://github.com/pieroxy/lz-string/
lz-string 旨在满足在 localStorage 中(尤其是在移动设备上)存储大量数据的需求。localStorage 通常限制为 5MB ~10MB,你可以通过对数据进行压缩,以存储更多的数据。
「示例」
- var string = "Hello, my name is semlinker";
- console.log("Size of sample is: " + string.length);
- var compressed = LZString.compress(string);
- console.log("Size of compressed sample is: " + compressed.length);
- string = LZString.decompress(compressed);
- console.log("Sample is: " + string);
下图是使用官方在线示例进行字符串压缩测试的结果:
(图片来源:https://pieroxy.net/blog/pages/lz-string/demo.html)
接下来我们开始来介绍一些主流的数据库。
二、主流的数据库
2.1 localForage
Offline storage, improved. Wraps IndexedDB, WebSQL, or localStorage using a simple but powerful API.
https://github.com/localForage/localForage
localForage 是一个快速简单的 JavaScript 存储库。它通过使用类似于 localStorage 的简单 API 来使用异步存储(IndexedDB 或 WebSQL)),进而改善你的 Web 应用程序的离线体验。
对于不支持 IndexedDB 或 WebSQL 的浏览器,localForage 会使用 localStorage 进行数据存储。此外,localForage 还支持存储所有可以序列化为 JSON 的原生 JS 对象以及 ArrayBuffers,Blob 和 TypedArrays。
localForage 主要支持的平台:
- IE 10(IE 8+ 使用 localStorage)
- Opera 15(Opera 10.5+ 使用 localStorage)
- Firefox 18
- Safari 3.1(包括 Mobile Safari)
- Chrome 23、Chrome for Android 32
- Phonegap/Apache Cordova 1.2.0
2.2 PouchDB
- PouchDB is a pocket-sized database.
https://github.com/pouchdb/pouchdb
PouchDB 是一个浏览器内数据库,允许应用程序在本地保存数据,以便用户即使在离线时也可以享受应用程序的所有功能。另外,数据在客户端之间是同步的,因此用户可以随时随地保持最新状态。
PouchDB 也在 Node.js 中运行,可以用作与 「CouchDB」 兼容的服务器的直接接口。该 API 在每个环境中工作都是相同的,因此你可以花更少的时间来担心浏览器的差异,而花更多的时间来编写干净、一致的代码。
PouchDB 支持所有现代浏览器:
- Firefox 29+ (Including Firefox OS and Firefox for Android)
- Chrome 30+
- Safari 5+
- Internet Explorer 10+
- Opera 21+
- Android 4.0+
- iOS 7.1+
- Windows Phone 8+
PouchDB 在幕后使用 IndexedDB,若当前环境不支持 IndexedDB 则回退到 Web SQL。
2.3 Rxdb
A realtime Database for JavaScript Applications.
https://github.com/pubkey/rxdb
RxDB(Reactive Database 的缩写)是 NoSQL 数据库,用于 JavaScript 应用程序,如网站,混合应用程序,Electron Apps,Progressive Web Apps 和 Node.js。响应式意味着你不仅可以查询当前状态,还可以订阅所有状态更改,比如查询的结果或文档的单个字段。
这对于基于 UI 的实时应用程序非常有用,因为它易于开发,并且具有很大的性能优势。为了在客户端和服务器之间复制数据,RxDB 提供了用于与任何 CouchDB 兼容端点以及自定义 GraphQL 端点进行实时复制的模块。
RxDB 支持以下特性:
- Mango-Query:支持 mquery API 从集合中获取数据,支持链式的 mongoDB 查询风格。
- Replication:因为 RxDB 依赖于 PouchDB,因此很容易实现终端设备与服务器之间的数据同步。
- Reactive:RxDB 使得同步 DOM 的状态变得很简单。
- MultiWindow/Tab:当 RxDB 的两个实例使用相同的存储引擎,它们的状态和操作流将会被广播。这意味着对于两个浏览器窗口,窗口 #1 的数据变化也会自动影响窗口 #2 的数据状态。
- Schema:通过 jsonschema 来定义 Schemas,它们用来描述数据格式。
- Encryption:通过将模式字段设置为encrypted,该字段的值将以加密模式存储,没有密码就无法读取。
2.4 NeDB
The JavaScript Database, for Node.js, nw.js, electron and the browser.
https://github.com/louischatriot/nedb
NeDB 是一个 JavaScript 数据库,能够运行在 Node.js、nw.js、Electron 和浏览器环境。它是使用纯的 JavaScript 实现,不依赖其它库,提供的 API 是 MongoDB API 的子集,重要的是它的速度非常快:
- 插入:10,680 ops/s
- 查找:43,290 ops/s
- 更新:8,000 ops/s。
- 删除:11,750 ops/s。
ops (operation per second) 即表示每秒操作的次数。
2.5 Dexie.js
A Minimalistic Wrapper for IndexedDB.
https://github.com/dfahlander/Dexie.js
Dexie.js 是 IndexedDB 的包装库,它提供了一套经过精心设计的 API,强大的错误处理,较强的可扩展性,此外它能够跟踪数据变化,支持 KeyRange (搜索不区分大小写,可设置匹方式和 OR 操作)。
Dexie.js 主要为了解决原生 IndexedDB API 中存在的三个主要问题:
- 异常错误处理。
- 较弱的查询功能。
- 代码复杂性。
为了便于开发者接入 Dexie.js,在 Dexie.js 官网中提供了丰富的示例:
- React + Dexie
- React + Redux + Dexie
- Dexie with Typescript
- Angular + Dexie
- Dexie with Electron
- Full Text Search
以上只列出部分示例,了解更多示例请访问:Dexie.js - Samples(
https://dexie.org/docs/Samples)。最后我们来简单介绍一下各种 Web 存储方案。
三、各种 Web 存储方案简介
3.1 Cookie
HTTP Cookie(也叫 Web Cookie 或浏览器 Cookie)是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。通常,它用于告知服务端两个请求是否来自同一浏览器,如保持用户的登录状态。
Cookie 主要用于以下三个方面:
- 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息);
- 个性化设置(如用户自定义设置、主题等);
- 浏览器行为跟踪(如跟踪分析用户行为等)。
Cookie 的特点:
- Cookie 的大小受限,一般为 4 KB;
- 同一个域名下存放 Cookie 的个数是有限制的,不同浏览器的个数不一样,一般为 20 个;
- Cookie 支持设置过期时间,当过期时自动销毁;
- 每次发起同域下的 HTTP 请求时,都会携带当前域名下的 Cookie;
- 支持设置为 HttpOnly,防止 Cookie 被客户端的 JavaScript 访问。
「示例1:简单用法」
- document.cookie = "name=semlinker";
- document.cookie = "favorite_food=tripe";
- alert(document.cookie);
- // 显示: name=semlinker;favorite_food=tripe
「示例2:得到名为 test2 的 cookie」
- document.cookie = "test1=Hello";
- document.cookie = "test2=World";
- var myCookie = document.cookie
- .replace(/(?:(?:^|.*;\s*)test2\s*\=\s*([^;]*).*$)|^.*$/, "$1");
- alert(myCookie);
3.2 localStorage
一种持久化的存储方式,也就是说如果不手动清除,数据就永远不会过期。它是采用键值对的方式存储数据,按域名将数据分别保存到对应数据库文件里。相比 Cookie 来说,它能保存更大的数据。
localStorage 的特点:
- 大小限制为 5MB ~10MB;
- 在同源的所有标签页和窗口之间共享数据;
- 数据仅保存在客户端,不与服务器进行通信;
- 数据持久存在且不会过期,重启浏览器后仍然存在;
- 对数据的操作是同步的。
「示例」
- // 通过setItem()增加一个数据项
- localStorage.setItem('myName', 'Semlinker');
- // 通过getItem()获取某个数据项
- let me = localStorage.getItem('myName');
- // 通过removeItem()移除某个数据项
- localStorage.removeItem('myName');
- // 移除所有数据项
- localStorage.clear();
3.3 sessionStorage
与服务端的 session 类似,sessionStorage 是一种会话级别的缓存,关闭浏览器时数据会被清除。需要注意的是 sessionStorage 的作用域是窗口级别的,也就是说不同窗口之间保存的 sessionStorage 数据是不能共享的。
- sessionStorage 的特点:
- sessionStorage 的数据只存在于当前浏览器的标签页;
- 数据在页面刷新后依然存在,但在关闭浏览器标签页之后数据就会被清除;
- 与 localStorage 拥有统一的 API 接口;
- 对数据的操作是同步的。
「示例」
- // 通过setItem()增加一个数据项
- sessionStorage.setItem('myName', 'Semlinker');
- // 通过getItem()获取某个数据项
- let me = sessionStorage.getItem('myName');
- // 通过removeItem()移除某个数据项
- sessionStorage.removeItem('myName');
- // 移除所有数据项
- sessionStorage.clear();
3.4 Web SQL
Web SQL 数据库 API 实际上不是 HTML5 规范的一部分,而是一个单独的规范,它引入了一组 API 来使用 SQL 来操作客户端数据库。需要注意的是,HTML5 已经放弃 Web SQL 数据库。
Web SQL Database 规范中定义的三个核心方法:
- openDatabase:这个方法使用现有数据库或新建数据库来创建数据库对象;
- transaction:这个方法允许我们根据情况控制事务的提交或回滚;
- executeSql:这个方法用于执行真实的 SQL 语句。
Web SQL 的特点(相比 Cookie、localStorage 与 sessionStorage):
- Web SQL 能方便进行对象存储;
- Web SQL 支持事务,能方便地进行数据查询和数据处理操作。
「示例」
- var db = openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024);
- db.transaction(function (tx) {
- // 执行查询操作
- tx.executeSql('CREATE TABLE IF NOT EXISTS LOGS (id unique, log)');
- // 执行插入操作
- tx.executeSql('INSERT INTO LOGS (id, log) VALUES (1, "foobar")');
- tx.executeSql('INSERT INTO LOGS (id, log) VALUES (2, "logmsg")');
- });
3.5 IndexedDB
IndexedDB 是一种底层 API,用于客户端存储大量结构化数据,包括文件、二进制大型对象。该 API 使用索引来实现对该数据的高性能搜索。虽然 Web Storage 对于存储较少量的数据很有用,但对于存储更大量的结构化数据来说,这种方法不太好用。IndexedDB 提供了一个解决方案。
IndexedDB 的特点:
- 存储空间大:存储空间可以达到几百兆甚至更多;
- 支持二进制存储:它不仅可以存储字符串,而且还可以存储二进制数据;
- IndexedDB 有同源限制,每一个数据库只能在自身域名下能访问,不能跨域名访问;
- 支持事务型:IndexedDB 执行的操作会按照事务来分组的,在一个事务中,要么所有的操作都成功,要么所有的操作都失败;
- 键值对存储:IndexedDB 内部采用对象仓库(object store)存放数据。所有类型的数据都可以直接存入,包括 JavaScript 对象。对象仓库中,数据以 “键值对” 的形式保存,每一个数据记录都有对应的主键,主键是独一无二的,不能有重复,否则会抛出一个错误。
- 数据操作是异步的:使用 IndexedDB 执行的操作是异步执行的,以免阻塞应用程序。
「示例」
- var dbName = "my_db";
- var request = indexedDB.open(dbName, 2);
- request.onerror = function(event) {
- // 错误处理
- };
- request.onupgradeneeded = function(event) {
- var db = event.target.result;
- // 建立一个对象仓库来存储我们客户的相关信息,我们选择 ssn 作为键路径(key path)
- // 因为 ssn 可以保证是不重复的
- var objectStore = db.createObjectStore("customers", { keyPath: "ssn" });
- // 建立一个索引来通过姓名来搜索客户。名字可能会重复,所以我们不能使用 unique 索引
- objectStore.createIndex("name", "name", { unique: false });
- // 使用邮箱建立索引,我们确保客户的邮箱不会重复,所以我们使用 unique 索引
- objectStore.createIndex("email", "email", { unique: true });
- // 使用事务的 oncomplete 事件确保在插入数据前对象仓库已经创建完毕
- objectStore.transaction.oncomplete = function(event) {
- // 将数据保存到新创建的对象仓库
- var customerObjectStore = db.transaction("customers", "readwrite").objectStore("customers");
- customerData.forEach(function(customer) {
- customerObjectStore.add(customer);
- });
- };
- };
篇幅有限这里我们只介绍了部分开源库,其实还有一些其它成熟的开源库,比如 lowdb(Local JSON Database)、Lovefield(Relational Database)和 LokiJS(NoSQL Database)等