数据存储

Data Storage/Persistence,离线存储、本地存储,[[003.浏览器组成部分|浏览器主要组成]] 部分之一

目前常见的浏览器离线存储的方式如下:

WebSQL

Warning

基本被废弃,了解一下就好,使用 IndexedDB 代替。

目前来看,WebSQL 已经不再是 W3C 推荐规范,官方也已经不再维护。原因说的很清楚,当前的 SQL 规范采用 SQLite 的 SQL 方言,而作为一个标准,这是不可接受的。

另外,WebSQL 使用 SQL 语言来进行操作,更像是一个关系型数据库;而 IndexedDB 则更像是一个 NoSQL 数据库, 这也是目前 W3C 强推的浏览端数据库解决方案。

所以本文不再对 WebSQL 做过多的介绍。

WebSQL 数据库引入了一组使用 SQL 操作客户端数据库的 APIs。如果之前接触过诸如像 MySQL 这样的关系型数据库,学习过 SQL 语言,那么 WebSQL 没有任何难度。

最新版的 Safari、Chrome 和 Opera 浏览器都支持 WebSQL。

在 WebSQL 中,有 3 个核心方法:

  1. openDatabase:使用现有的数据库或者新建的数据库创建一个数据库对象。
  2. transaction:控制事务,基于事务执行提交或者回滚。
  3. executeSql:执行实际的 SQL 查询。

打开数据库

使用 openDatabase() 方法来打开已存在的数据库,如果数据库不存在,则会创建一个新的数据库,使用代码如下:

const db = openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024);

openDatabase() 方法对应的 5 个参数:

  1. 数据库名称
  2. 版本号
  3. 描述文本
  4. 数据库大小
  5. 创建回调:会在创建数据库后被调用

执行操作

const id = '1'
const log = 'test'

db.transaction(function (tx) {
	// tx 是一个 SQLTransaction 对象,代表一个事务
    tx.executeSql('CREATE TABLE IF NOT EXISTS logs (id unique, log)');
    tx.executeSql('INSERT INTO logs (id, log) VALUES (?, ?)', [id, log]);
});

使用动态值来插入数据,避免 SQL 注入

同一事务中,只要发生错误,所有 SQL 都会回滚

executeSql(sqlStatement, arguments, callback, errorCallback)

executeSql 完整的语法接收 4 个参数,分别是:

  1. SQL 语句:可以包含动态值 ?
  2. 参数:SQL 动态值传参
  3. 成功回调
  4. 错误回调

IndexedDB

简介

随着浏览器的功能不断增强,越来越多的网站开始考虑,将大量数据储存在客户端,这样可以减少从服务器获取数据,直接从本地获取数据。

现有的浏览器数据储存方案,都不适合储存大量数据:

所以,需要一种新的解决方案,这就是 IndexedDB 诞生的背景。

MDN 官网是这样解释 IndexedDB 的:

IndexedDB 是一种底层 API,用于在客户端存储大量的结构化数据(可以存储 结构化克隆算法 支持的任何对象,包括文件、二进制大型对象 Blob 等)。该 API 使用索引实现对数据的高性能搜索。虽然 Web Storage 在存储较少量的数据很有用,但对于存储更大量的结构化数据来说力不从心。而 IndexedDB 提供了这种场景的解决方案。

通俗地说,IndexedDB 就是浏览器提供的本地数据库,它可以被网页脚本创建和操作。IndexedDB 允许储存大量数据,提供查找接口,还能建立索引。这些都是 LocalStorage 所不具备的。就数据库类型而言,IndexedDB 不属于关系型数据库(不支持 SQL 查询语句),更接近 NoSQL 数据库。

下表罗列出了几种常见的客户端存储方式的对比:

存储大小 失效时间
会话期 Cookie 4KB 浏览器关闭自动清除
持久性 Cookie 4KB 设置过期时间,到期后清除
SessionStorage 2.5~10MB 浏览器关闭后清除
LocalStorage 2.5~10MB 永久保存(除非手动清除)
WebSQL 已废弃 已废弃
IndexedDB >250MB 手动更新或删除

IndexedDB 具有以下特点:

IndexedDB 主要使用在于客户端需要存储大量的数据的场景下:

重要概念

IndexedDB 是一个比较复杂的 API,涉及不少概念。它把不同的实体,抽象成一个个对象接口。学习这个 API,就是学习它的各种对象接口。

数据库

数据库是一系列相关数据的容器。每个 host(协议 + 域名 + 端口)都可以新建任意多个数据库。

IndexedDB 数据库有版本的概念。同一个时刻,只能有一个版本的数据库存在。如果要修改数据库结构(新增或删除表、索引或者主键),只能通过升级数据库版本完成。

对象仓库

每个数据库包含若干个对象仓库(object store)。它类似于关系型数据库的数据表。

数据记录

对象仓库保存的是数据记录。每条记录类似于关系型数据库的行,但是只有主键和数据体两部分。主键用来建立默认的索引,必须是不同的,否则会报错。主键可以是数据记录里面的一个属性,也可以指定为一个递增的整数编号。

数据体可以是任意数据类型,不限于对象。

索引

为了加速数据的检索,可以在对象仓库里面,为不同的属性建立索引。

在关系型数据库当中也有索引的概念,可以给对应的表字段添加索引,以便加快查找速率。在 IndexedDB 中同样有索引,可以在创建 store 的时候同时创建索引,在后续对 store 进行查询的时候即可通过索引来筛选。

给某个字段添加索引后,在后续插入数据的过程中,索引字段便不能为空。

事务

数据记录的读写和删改,都要通过事务完成。事务对象提供 errorabortcomplete 三个事件,用来监听操作结果。

操作请求

indexedDB.open() 方法的返回值,可以设置 onsuccess(数据库打开成功)、onerror(数据库打开失败)、onupgradeneeded(数据库有更新:数据库创建、版本号更新、增删数据表) 等事件的回调函数

指针(游标)

游标是 IndexedDB 数据库新的概念,可以把游标想象为一个指针,比如我们要查询满足某一条件的所有数据时,就需要用到游标,我们让游标一行一行的往下走,游标走到的地方便会返回这一行数据,此时便可对此行数据进行判断,是否满足条件。

使用

想了解 IndexedDB 相关的更多 API,可以扩展阅读:MDN 文档网道

由于 IndexedDB 所提供的原生 API 比较复杂,所以现在也出现了基于 IndexedDB 封装的库。例如 Dexie.js

File API

介绍

HTML 的 input 表单控件,其 type 属性可以设置为 file,表示这是一个上传控件。

<input type="file" name="" multiple>

现在又有了 HTML5 提供的 File API,该接口允许 JavaScript 读取本地文件,但并不能直接访问本地文件,而是要依赖于用户行为,比如用户在 type="file" 控件上选择了某个文件或者用户将文件拖拽到浏览器上。

File API 提供了以下几个接口来访问本地文件系统:

File

File 对象代表一个文件,用来读写文件信息。它继承了 Blob 对象,或者说是一种特殊的 Blob 对象,所有可以使用 Blob 对象的场合都可以使用它。

最常见的使用场合是表单的文件上传控件(<input type="file">),用户选中文件以后,浏览器就会生成一个数组,里面是每一个用户选中的文件,它们都是 File 实例对象。

<input type="file" name="" id="file">
const file = document.getElementById('file');
file.onchange = function(event) {
    const files = event.target.files;
    console.log(files); // FileList
    console.log(files[0] instanceof File); // true
}

![[2021-12-02-022135.png]]

构造函数

浏览器原生提供一个 File() 构造函数,用来生成 File 实例对象。

new File(array, name [, options])

File() 构造函数接受三个参数:

  1. array:可以是二进制对象或字符串,表示文件的内容。
  2. name:字符串,表示文件名或文件路径。
  3. options:配置对象,设置实例的属性。该参数可选。

实例属性和实例方法

File 对象有以下实例属性:

File 继承了 Blob,因此可以使用 Blob 的实例方法,如:slice()

FileList

FileList 对象是一个类数组对象,代表一组选中的文件,每个成员都是一个 File 实例。

在 [[#File 对象|上面的那个示例]] 中,我们就可以看到触发 change 事件后,event.target.files 拿到的就是一个 FileList 实例对象。

它主要出现在两个场合:

FileList 的实例属性主要是 length,表示包含多少个文件。

FileList 的实例方法主要是 item(),用来返回指定位置的实例。它接受一个整数作为参数,表示位置的序号(从零开始)。

但是,由于 FileList 的实例是一个类数组对象,可以直接用方括号运算符,即 myFileList[0] 等同于 myFileList.item(0),所以一般用不到 item() 方法。

FileReader

FileReader 用于读取 File 对象或 Blob 对象所包含的文件内容。

浏览器原生提供一个 FileReader 构造函数,用来生成 FileReader 实例。

const reader = new FileReader();

实例属性

实例方法

File System Access API

看上去上面的 File API 还不错,能够读取到本地的文件,但是它和离线存储有啥关系?

我们要的是离线存储功能,能够将数据存储到本地。

确实 File API 只能够做读取的工作,但是有一套新的 API 规范又推出来了,叫做 File System Access API。

是的,这是两套规范,千万没弄混淆了。

关于 File System Access API,这套方案应该是未来的主角。它提供了比较稳妥的本地文件交互模式,即保证了实用价值,又保障了用户的数据安全。

这个 API 对前端来说意义不小。有了这个功能,Web 可以提供更完整的功能链路,从打开、到编辑、到保存,一套到底。不过遗憾的是目前兼容性较差。

![[Pasted image 20240320135339.png]]

目前针对该 API 的相关资料比较少,如果对该 API 感兴趣,下面给出两个扩展阅读资料: