1 前言
前端有多种本地存储方案可供选择,以下是其中一些常见的方案:
- Cookie:Cookie是一种小型的文本文件,可以在浏览器中存储少量数据。Cookie通常用于存储会话信息或用户偏好设置等数据(只能存储少量数据)。
- Web Storage:Web Storage是HTML5引入的API,包括localStorage和sessionStorage。这两种存储方式都可以在浏览器中存储大量数据,并且数据可以跨页面和会话保持不变(虽然比cookie多,但是同样有上限(5M)左右)。
- IndexedDB:IndexedDB是一种高级的客户端存储API,可以在浏览器中存储大量结构化数据。IndexedDB支持事务和索引等高级功能,并且可以在离线状态下使用(api多且繁琐,存储量大、高版本浏览器兼容性较好)。
- Web SQL Database:Web SQL Database是一种基于SQL的客户端存储API,已经被废弃。虽然Web SQL Database已经不再被推荐使用,但在某些情况下仍然可以考虑使用(使用简单,存储量大,兼容性差)。
- File API:File API是HTML5引入的API,可以让浏览器访问用户的本地文件系统。File API可以用于读取和写入本地文件,也可以用于将数据保存到本地文件中(访问用户本地文件系统存在安全性问题、兼容性差、读取和写入大型文件可能会导致浏览器崩溃或变得缓慢)。
这些本地存储方案各有优缺点,没有一种方案可以完全取代其他方案,有没有一种能够集合这多种方式, 遵循“渐进增强”或“优雅降级”的原则的方案呢(在现代浏览器中使用最新的本地存储技术,并在旧浏览器中回退到较旧的技术,确保应用程序在不同浏览器和平台上的兼容性)。是有的
pouchdb 和 localForage 都是前端本地存储的库,它们都支持多种存储后端,并提供了一致的API来存储和检索数据,这些库都遵循“渐进增强”或“优雅降级”的原则。
localForage
优点:
-
- 使用异步API封装了Web Storage、IndexedDB和WebSQL的库,提供了简单易用的方法来存储和检索数据。
- 可以在不同浏览器和平台上提供一致的API。
- API 相对简单,易于上手。
缺点:
-
- 不支持复制和同步功能。
- 不支持内存存储后端。
使用场景:
-
- 对性能要求较高的应用程序。
- 不需要在多个客户端之间同步数据的应用程序。
- 对API易用性有较高要求的应用程序
pouchdb
优点:
-
- 支持多种存储后端,包括IndexedDB、WebSQL、LevelDB和内存等。
- 提供了复制和同步功能,使得多个客户端之间可以共享数据。
- 可以在浏览器和Node.js环境中使用。
缺点:
-
- API 相对较复杂,需要一定的学习成本。
- 在处理大量数据时可能会导致性能问题。
使用场景:
-
- 需要在多个客户端之间同步数据的应用程序。
- 需要使用多种存储后端的应用程序。
- 对性能要求不是特别高的应用程序。
基于localForage功能强大、易于使用、跨浏览器和平台支持的特点,本文基于localForage进行介绍
2 localForage
localForage 是一个 JavaScript 库,通过简单类似 localStorage API 的异步存储来改进你的 Web 应用程序的离线体验。它能存储多种类型的数据,而不仅仅是字符串。
官方提供了中文文档
2.1 兼容性
localForage 有一个优雅降级策略,若浏览器不支持 IndexedDB 或 WebSQL,则使用 localStorage。在所有主流浏览器中都可用:Chrome,Firefox,IE 和 Safari(包括 Safari Mobile)。下面是 indexDB、web sql、localStorage 的一个浏览器支持情况,可以发现,兼容性方面loaclForage基本上满足99%需求
2.2 存储量
localForage 的存储量取决于所使用的存储后端。localForage 支持多种存储后端,包括 Web Storage、IndexedDB 和 WebSQL,每种存储后端的存储容量不同。
Web Storage 存储后端通常具有较小的存储容量限制,通常为 5-10 MB。IndexedDB 和 WebSQL 存储后端通常具有更大的存储容量限制,可以存储数百 MB 或更多的数据。
需要注意的是,存储容量限制也取决于浏览器和操作系统。例如,移动设备上的浏览器通常具有更小的存储容量限制,因为它们的存储空间有限。
总之,localForage 的存储量取决于所使用的存储后端和浏览器/操作系统的限制
2.3 使用
解决了兼容性和存储量的点,来看看localforage的基础用法
2.3.1 安装
# 通过 npm 安装:
npm install localforage
# 或通过 bower:
<script src="localforage.js"></script>
<script>console.log('localforage is: ', localforage);</script>
2.3.2 增删查改
获取存储
getItem(key, successCallback)
从仓库中获取 key 对应的值并将结果提供给回调函数。如果 key 不存在,getItem()
将返回 null
。
localforage.getItem('somekey').then(function(value) {
// 当离线仓库中的值被载入时,此处代码运行
console.log(value);
}).catch(function(err) {
// 当出错时,此处代码运行
console.log(err);
});
// 回调版本:
localforage.getItem('somekey', function(err, value) {
// 当离线仓库中的值被载入时,此处代码运行
console.log(value);
});
💡
当存储 undefined
时, getItem()
也会返回 null
。由于 localStorage 限制,同时出于兼容性的原因 localForage 无法存储 undefined
。
设置存储
setItem(key, value, successCallback)
将数据保存到离线仓库。你可以存储如下类型的 JavaScript 对象:
Array
ArrayBuffer
Blob
Float32Array
Float64Array
Int8Array
Int16Array
Int32Array
Number
Object
Uint8Array
Uint8ClampedArray
Uint16Array
Uint32Array
String
localforage.setItem('somekey', 'some value').then(function (value) {
// 当值被存储后,可执行其他操作
console.log(value);
}).catch(function(err) {
// 当出错时,此处代码运行
console.log(err);
});
// 不同于 localStorage,你可以存储非字符串类型
localforage.setItem('my array', [1, 2, 'three']).then(function(value) {
// 如下输出 `1`
console.log(value[0]);
}).catch(function(err) {
// 当出错时,此处代码运行
console.log(err);
});
// 你甚至可以存储 AJAX 响应返回的二进制数据
req = new XMLHttpRequest();
req.open('GET', '/photo.jpg', true);
req.responseType = 'arraybuffer';
req.addEventListener('readystatechange', function() {
if (req.readyState === 4) { // readyState 完成
localforage.setItem('photo', req.response).then(function(image) {
// 如下为一个合法的 <img> 标签的 blob URI
var blob = new Blob([image]);
var imageURI = window.URL.createObjectURL(blob);
}).catch(function(err) {
// 当出错时,此处代码运行
console.log(err);
});
}
});
🔔
当使用 localStorage 和 WebSQL 作为后端时,二进制数据在保存(和检索)之前会被序列化。在保存二进制数据时,序列化会导致大小增大。
删除存储
removeItem(key, successCallback)
从离线仓库中删除 key 对应的值。
localforage.removeItem('somekey').then(function() {
// 当值被移除后,此处代码运行
console.log('Key is cleared!');
}).catch(function(err) {
// 当出错时,此处代码运行
console.log(err);
});
清空存储
clear(successCallback)
从数据库中删除所有的 key,重置数据库。
localforage.clear()
将会删除离线仓库中的所有值。谨慎使用此方法。
localforage.clear().then(function() {
// 当数据库被全部删除后,此处代码运行
console.log('Database is now empty.');
}).catch(function(err) {
// 当出错时,此处代码运行
console.log(err);
});
2.3.3 其他方法
可参考 官方文档
获取离线仓库中的 key 的数量
length(successCallback)
获取离线仓库中的 key 的数量(即数据仓库的“长度”)
localforage.length().then(function(numberOfKeys) {
// 输出数据库的大小
console.log(numberOfKeys);
}).catch(function(err) {
// 当出错时,此处代码运行
console.log(err);
});
根据 key 的索引获取其名
key(keyIndex, successCallback)
根据 key 的索引获取其名
localforage.key(2).then(function(keyName) {
// key 名
console.log(keyName);
}).catch(function(err) {
// 当出错时,此处代码运行
console.log(err);
});
获取数据仓库中所有的 key
keys(successCallback)
获取数据仓库中所有的 key。
localforage.keys().then(function(keys) {
// 包含所有 key 名的数组
console.log(keys);
}).catch(function(err) {
// 当出错时,此处代码运行
console.log(err);
});
迭代数据仓库中的所有 value/key 键值对
iterate(iteratorCallback, successCallback)
迭代数据仓库中的所有 value/key 键值对。
iteratorCallback
在每一个键值对上都会调用一次,其参数如下: 1. value 为值 2. key 为键名 3. iterationNumber 为迭代索引 - 数字
// 同样的代码,但使用 ES6 Promises
localforage.iterate(function(value, key, iterationNumber) {
// 此回调函数将对所有 key/value 键值对运行
console.log([key, value]);
}).then(function() {
console.log('Iteration has completed');
}).catch(function(err) {
// 当出错时,此处代码运行
console.log(err);
});
// 提前退出迭代:
localforage.iterate(function(value, key, iterationNumber) {
if (iterationNumber < 3) {
console.log([key, value]);
} else {
return [key, value];
}
}).then(function(result) {
console.log('Iteration has completed, last iterated pair:');
console.log(result);
}).catch(function(err) {
// 当出错时,此处代码运行
console.log(err);
});
📌
通过在 iteratorCallback
回调函数中返回一个非 undefined
的值,能提前退出 iterate
。iteratorCallback
的返回值即作为整个迭代的结果,将被传入 successCallback
。
这意味着如果你使用的是 CoffeeScript,那么你需要手动执行一个不带内容的 return
语句才能继续迭代所有的 key/value 键值对。
2.3.4 初始化配置
选择特定存储引擎
setDriver(driverName)
setDriver([driverName, nextDriverName])
若可用,强制设置特定的驱动。
默认情况下,localForage 按照以下顺序选择数据仓库的后端驱动:
- IndexedDB
- WebSQL
- localStorage
如果你想强制使用特定的驱动,可以使用 setDriver()
,参数为以下的某一个或多个:
localforage.INDEXEDDB
localforage.WEBSQL
localforage.LOCALSTORAGE
// 强制设置 localStorage 为后端的驱动
localforage.setDriver(localforage.LOCALSTORAGE);
// 列出可选的驱动,以优先级排序
localforage.setDriver([localforage.WEBSQL, localforage.INDEXEDDB]);
🔔
通过使用 localforage.setDriver()
方法,可以选择要在其中存储数据的特定存储引擎。如果你尝试加载的后端驱动在浏览器上不可用,localForage 将使用以前使用的后端驱动中的一个。这意味着如果你试图强制 Gecko 浏览器使用 WebSQL,则会失败并继续使用 IndexedDB。
数据库配置
config(options)
设置 localForage 选项。在调用 localForage 前必先调用它,但可以在 localForage 被加载后调用。使用此方法设置的任何配置值都将保留,即使在驱动更改后,所以你也可以先调用 config()
再次调用 setDriver()
。以下配置值可以设置:
driver
要使用的首选驱动。与上面的 setDriver 的值格式相同。
默认值:[localforage.INDEXEDDB, localforage.WEBSQL, localforage.LOCALSTORAGE]
name
数据库的名称。可能会在在数据库的提示中会出现。一般使用你的应用程序的名字。在 localStorage 中,它作为存储在 localStorage 中的所有 key 的前缀。
默认值:'localforage'
size
数据库的大小(以字节为单位)。现在只用于WebSQL。 默认值:4980736
storeName
数据仓库的名称。在 IndexedDB 中为 dataStore
,在 WebSQL 中为数据库 key/value 键值表的名称。仅含字母和数字和下划线。任何非字母和数字字符都将转换为下划线。
默认值:'keyvaluepairs'
version
数据库的版本。将来可用于升级; 目前未使用。
默认值:1.0
description
数据库的描述,一般是提供给开发者的。
默认值:''
// 将数据库从 “localforage” 重命名为 “Hipster PDA App”
localforage.config({
name: 'Hipster PDA App'
});
// 将强制使用 localStorage 作为存储驱动,即使其他驱动可用。
// 可用配置代替 `setDriver()`。
localforage.config({
driver: localforage.LOCALSTORAGE,
name: 'I-heart-localStorage'
});
// 配置不同的驱动优先级
localforage.config({
driver: [localforage.WEBSQL,
localforage.INDEXEDDB,
localforage.LOCALSTORAGE],
name: 'WebSQL-Rox'
});
📌
与大多数 localForage API 不同,该 config
方法是同步的。
2.3.5 自定义驱动
你需要确保接受一个 callback
参数,并且将同样的几个参数传递给回调函数,类似默认驱动那样。同时你还需要 resolve 或 reject Promise。通过 默认驱动 可了解如何实现自定义的驱动。
自定义实现可包含一个 _support
属性,该属性为布尔值(true
/ false
) ,或者返回一个 Promise
,该 Promise
的结果为布尔值。如果省略 _support
,则默认值是 true
。你用它来标识当前的浏览器支持你自定义的驱动。
// 此处为驱动的实现
var myCustomDriver = {
_driver: 'customDriverUniqueName',
_initStorage: function(options) {
// 在此处自定义实现...
},
clear: function(callback) {
// 在此处自定义实现...
},
getItem: function(key, callback) {
// 在此处自定义实现...
},
key: function(n, callback) {
// 在此处自定义实现...
},
keys: function(callback) {
// 在此处自定义实现...
},
length: function(callback) {
// 在此处自定义实现...
},
removeItem: function(key, callback) {
// 在此处自定义实现...
},
setItem: function(key, value, callback) {
// 在此处自定义实现...
}
}
// 为 localForage 添加驱动。
localforage.defineDriver(myCustomDriver);
💡
你在任何一个 localForage 实例上添加驱动实现后,则该驱动可用于页面内的所有 localForage 实例。
返回正在使用的驱动的名称
driver()
返回正在使用的驱动的名称,在异步的驱动初始化过程中(详情参阅 ready)为 null
,若初始化未能找到可用的驱动也为 null
。
localforage.driver();
// "asyncStorage"
如果驱动在初始化过程中或之后出错,localForage 将试着使用下一个驱动。由加载 localForage 时的默认驱动顺序或传递给 setDriver()
的驱动顺序决定。
异步驱动程序初始化过程是否已完成
ready()
提供了一种方法来确定异步驱动程序初始化过程是否已完成,localForage 会对所有数据 API 方法的调用进行缓冲排序。当我们需要知道 localForage 当前正在使用的是哪一个驱动时,此方法会非常有用。
localforage.ready().then(function() {
// 当 localforage 将指定驱动初始化完成时,此处代码运行
console.log(localforage.driver()); // LocalStorage
}).catch(function (e) {
console.log(e); // `No available storage method found.`
// 当没有可用的驱动时,`ready()` 将会失败
});
浏览器是否支持 driverName
supports(driverName)
返回一个布尔值,表示浏览器是否支持 driverName
。
localforage.supports(localforage.INDEXEDDB);
// true
2.3.6多实例
可以创建多个 localForage 实例,且能指向不同数据仓库。所有 config 中的配置选项都可用。
createInstance
创建并返回一个 localForage 的新实例。每个实例对象都有独立的数据库,而不会影响到其他实例。
var store = localforage.createInstance({
name: "nameHere"
});
var otherStore = localforage.createInstance({
name: "otherName"
});
// 设置某个数据仓库 key 的值不会影响到另一个数据仓库
store.setItem("key", "value");
otherStore.setItem("key", "value2");
dropInstance
调用时,若不传参,将删除当前实例的 “数据仓库” 。
调用时,若参数为一个指定了 name
和 storeName
属性的对象,会删除指定的 “数据仓库”。
调用时,若参数为一个仅指定了 name
属性的对象,将删除指定的 “数据库”(及其所有数据仓库)。
localforage.dropInstance().then(function() {
console.log('Dropped the store of the current instance').
});
localforage.dropInstance({
name: "otherName",
storeName: "otherStore"
}).then(function() {
console.log('Dropped otherStore').
});
localforage.dropInstance({
name: "otherName"
}).then(function() {
console.log('Dropped otherName database').
});