- 前端
- 缓存抽象
【免费下载链接】localForage
💾 Offline storage, improved. Wraps IndexedDB, WebSQL, or localStorage using a simple but powerful API.
localForage 是一个面向 JavaScript 的快速、简洁的离线存储库,它用一套与localStorage高度一致的简单 API 封装了 IndexedDB、WebSQL 与 localStorage 三种浏览器存储后端,并自动完成异步化与驱动回退。本文以本仓库 README.md 为主体,结合 src/localforage.js 及各驱动源码,完整讲解安装方式、双 API 用法、配置项语义、多实例、自定义驱动与本地开发测试,帮助你在真实项目中安全落地离线数据存储。
什么是 localForage
localForage 的核心设计目标可以用一句话概括:用异步存储(IndexedDB 或 WebSQL)配合简单、localStorage风格的 API,改善 Web 应用的离线体验。它解决了原生浏览器存储的三大痛点:
localStorage是同步 API,在主线程上做字符串序列化会阻塞 UI;localStorage容量有限(通常 5MB 左右),且只能存字符串;- IndexedDB / WebSQL 虽然强大,但原生 API 冗长、回调繁琐,学习与使用成本高。
localForage 在支持 IndexedDB 或 WebSQL 的浏览器中优先使用异步后端,在没有二者的浏览器(或隐私模式下两者被禁用)中自动回退到 localStorage,从而在不同环境下提供一致的编程体验。
仓库 package.json 显示当前版本为1.10.0,由 Mozilla 维护,采用 Apache-2.0 许可,唯一的运行时依赖是 Promise polyfill 库lie(见 src/utils/promise.js,当全局Promise不存在时自动加载lie/polyfill)。
安装与快速开始
通过 script 标签引入
localForage 设计为"丢一个 JS 文件进页面即可用"。引入构建产物后,全局会暴露localforage对象:
<script src="localforage/dist/localforage.js"></script> <script>localforage.getItem('something', myCallback);</script>仓库根目录的dist构建产物由 Grunt 任务生成(见 Gruntfile.js 与 package.json 中的build脚本),源码位于 src/localforage.js,入口默认导出的是一个已经初始化好的单例new LocalForage()。
通过 npm 安装
npm install localforage安装后在支持 ES Module / CommonJS 的构建工具中直接import localforage from 'localforage'即可使用。
最小示例
// 写 localforage.setItem('key', 'value'); // 读 localforage.getItem('key').then(function (value) { console.log(value); });核心 API:Callback 与 Promise 双形态
因为 localForage 走的是异步存储,所以它的 API 天然是异步的;除此之外,调用方式与localStorageAPI 几乎一一对应(getItem/setItem/removeItem/clear/key/keys/length)。它同时提供 Node 风格回调与 Promise 两种形态,官方建议优先使用 Promise。
Node 风格回调
localforage.setItem('key', 'value', function (err) { // 如果 err 非 null,说明出错了 localforage.getItem('key', function (err, value) { // 如果 err 非 null,说明出错了;否则 value 就是取到的值 }); });注意回调签名的统一约定:错误永远在第一个参数,成功数据在第二个参数。
Promise 链式写法
localforage.setItem('key', 'value').then(function () { return localforage.getItem('key'); }).then(function (value) { // 这里拿到 value }).catch(function (err) { // 出错处理 });async/await 写法
try { const value = await localforage.getItem('somekey'); // 值从离线存储中加载完成后,此代码才会执行 console.log(value); } catch (err) { // 出错处理 console.log(err); }从源码结构看,这套"双 API"是由 src/utils/executeCallback.js 与 src/utils/executeTwoCallbacks.js 实现的:每个驱动方法内部先构造 Promise,再通过这两个工具把可选的回调接入 Promise 的成功/失败路径,从而让同一份实现同时支持回调和 Promise 两种调用方式。
完整的 API 清单
除读写外,localForage 还提供以下方法(全部支持回调与 Promise 双形态):
| 方法 | 说明 |
|---|---|
getItem(key) | 读取键对应的值,不存在时返回null |
setItem(key, value) | 写入键值,resolve 时返回被写入的值 |
removeItem(key) | 删除一个键 |
clear() | 清空当前实例的所有数据 |
length() | 返回当前 store 中键的数量 |
key(n) | 返回第 n 个键(IndexedDB 下用游标advance(n)实现,见 src/drivers/indexeddb.js) |
keys() | 返回所有键的数组 |
iterate(iterator) | 遍历所有键值,迭代器返回非undefined值时立即终止遍历 |
dropInstance(options) | 按数据库名/存储名删除整个实例(可选方法,见下文自定义驱动一节) |
可以存储任意类型:Blob、TypedArray 与普通 JS 对象
与只能存字符串的localStorage不同,localForage 可以存储任意类型:所有能被 JSON 序列化的原生 JS 对象,以及 ArrayBuffer、Blob、TypedArray。即使底层后端是 localStorage,localForage 也会在读写时自动执行JSON.stringify()/JSON.parse(),所以这些类型依然可用——唯一的限制是 localStorage 的容量约束决定了它装不下太多大 Blob。
这一能力在 IndexedDB 后端由 src/drivers/indexeddb.js 原生支持;在 WebSQL / localStorage 后端则由 src/utils/serializer.js 实现。序列化器的工作原理非常值得了解:
- 普通对象走
JSON.stringify/JSON.parse; - ArrayBuffer 与各 TypedArray(Int8Array、Uint8Array、Uint8ClampedArray、Int16Array、Uint16Array、Int32Array、Uint32Array、Float32Array、Float64Array)会被转换为 Base64 字符串,并加上
__lfsc__:前缀与类型标记(如arbf、ui08、fl32); - Blob 会被
FileReader读出为 ArrayBuffer 后同样转成带blob类型标记的字符串,type信息通过~~local_forage_type~前缀保留; - 反序列化时依据标记还原出原始类型(见 serializer.js 中的
deserialize)。
而在 IndexedDB 后端,当浏览器不支持原生 Blob 存储(如 Chrome < 43,详见 indexeddb.js 中的_checkBlobSupport与_encodeBlob)时,驱动会先把 Blob 编码为带__local_forage_encoded_blob标记的对象再入库,读取时解码还原,保证跨浏览器行为一致。
配置详解:config() 的六个选项
通过config()方法可以设置数据库信息,可用选项为:driver、name、storeName、version、size、description。完整示例(与 README 一致):
localforage.config({ driver : localforage.WEBSQL, // 强制使用 WebSQL;等价于调用 setDriver() name : 'myApp', version : 1.0, size : 4980736, // 数据库大小,单位字节。目前仅 WebSQL 生效。 storeName : 'keyvaluepairs', // 应为字母数字,可带下划线 description : 'some description' });各选项的语义与实现细节(结合 src/localforage.js 的DefaultConfig与config()方法):
| 选项 | 默认值 | 说明 |
|---|---|---|
driver | ['asyncStorage', 'webSQLStorage', 'localStorageWrapper'](按序回退) | 可传字符串或字符串数组,见下文"驱动选择与回退"一节 |
name | 'localforage' | 数据库名,IndexedDB/WebSQL 中对应真实数据库名;localStorage 后端中作为键前缀的一部分 |
storeName | 'keyvaluepairs' | 存储区名,对应 IndexedDB 的 object store、WebSQL 的数据表;localStorage 后端中拼入键前缀。源码会对该值做清洗:options[i].replace(/\W/g, '_'),即把非字母数字字符统一替换为下划线 |
version | 1.0 | 数据库版本号。源码强制校验:typeof options[i] !== 'number'时直接返回Error('Database version must be a number.') |
size | 4980736 | 数据库大小(字节)。默认值刻意设为略低于 5MB,因为这是无需弹窗即可使用的最大配额。注释明确说明"WebSQL-only for now" |
description | '' | 数据库描述,仅作元信息 |
使用 config() 的三个关键注意点
- 必须在读写之前调用:README 明确强调,必须在调用
getItem()、setItem()、removeItem()、clear()、key()、keys()、length()等任何数据操作之前调用config()。源码也印证了这一点——config()内部会检查this._ready,如果实例已经就绪,会直接返回错误"Can't call config() after localforage has been used."。 config()兼具 getter 功能:传入字符串时返回对应配置值(config('name')),不传参数时返回整个配置对象。- 传入
driver时:配置完成后会自动触发setDriver(this._config.driver),等价于手动调用setDriver()。
多实例:createInstance
不同业务模块可能需要互相隔离的存储区。createInstance可以创建指向不同 store 的多个 localForage 实例,config()支持的全部选项它都支持:
var store = localforage.createInstance({ name: "nameHere" }); var otherStore = localforage.createInstance({ name: "otherName" }); // 在一个实例上写 key,不影响另一个实例 store.setItem("key", "value"); otherStore.setItem("key", "value2");从源码看,createInstance(options)就是return new LocalForage(options)(见 src/localforage.js),每个实例拥有独立的_config、_dbInfo与驱动状态。有意思的是,IndexedDB 驱动内部通过dbContexts按数据库名维护共享上下文(见 src/drivers/indexeddb.js 的createDbContext):同一数据库名下的多个实例会共享同一个数据库连接,并通过_deferReadiness/_advanceReadiness把每个实例的ready()串成一条就绪链,避免并发打开/升级数据库时的竞争条件;同时每个实例用不同的storeName即可实现互不干扰的逻辑隔离。这也解释了为什么多个实例指向同一数据库时,实例间的初始化会相互等待。
驱动选择与自动回退机制
localForage 内置三个驱动(见 src/localforage.js 的DefaultDrivers),默认按以下顺序尝试:
- IndexedDB(
asyncStorage,src/drivers/indexeddb.js)——容量大、支持任意类型、异步,是首选; - WebSQL(
webSQLStorage,src/drivers/websql.js)——基于 SQLite 的异步存储; - localStorage(
localStorageWrapper,src/drivers/localstorage.js)——最后的兜底方案。
驱动可用性由各自的_support检测函数决定,并缓存在DriverSupport中(localforage.supports(driverName)可查询):
- IndexedDB:见 src/utils/isIndexedDBValid.js。它做了非常细致的检测——排除被伪装成 Safari 的 IE Mobile;对 Safari 要求同时存在原生
fetch(用于排除 Safari < 10.1 对 IDB 支持不达标的情况);要求存在indexedDB与IDBKeyRange(后者用于排除三星、HTC 等 Android < 4.4 设备的残缺实现); - WebSQL:见 src/utils/isWebSQLValid.js,仅判断
typeof openDatabase === 'function'; - localStorage:见 src/utils/isLocalStorageValid.js。
回退逻辑实现在setDriver()中(src/localforage.js):先通过_getSupportedDrivers()过滤出当前环境支持的驱动,再在initDriver的driverPromiseLoop中逐个尝试——前一个驱动初始化失败就 catch 后尝试下一个;全部失败则 reject 并抛出'No available storage method found.'。因此你也可以手动指定驱动顺序,例如:
// 只尝试 IndexedDB 与 localStorage localforage.setDriver([localforage.INDEXEDDB, localforage.LOCALSTORAGE]);localStorage 后端还有一层额外防护:初始化时通过checkIfLocalStorageThrows()实际写入一个测试键来探测存储是否可用,并专门处理了 Safari 隐私模式下配额为 0 的情况(见 localstorage.js 的_isLocalStorageUsable);写入时捕获QuotaExceededError/NS_ERROR_DOM_QUOTA_REACHED并作为 Promise 拒绝抛出。localStorage 后端的键以name/与(非默认时)storeName/作为前缀(_getKeyPrefix),从而与页面中其他直接使用 localStorage 的代码隔离。
与 RequireJS / 模块化工具搭配使用
localForage 以 UMD 形式发布,可直接与 RequireJS 配合:
define(['localforage'], function(localforage) { // 回调形式: localforage.setItem('mykey', 'myvalue', console.log); // Promise 形式: localforage.setItem('mykey', 'myvalue').then(console.log); });仓库 examples/require.html 与 examples/require.js 提供了可运行的 RequireJS 示例。
TypeScript 类型支持
仓库自带类型声明 typings/localforage.d.ts(package.json的typings字段指向它),其中定义了LocalForageOptions(driver、name、storeName、size、version、description,与config()完全对应)、LocalForageDbMethodsCore(getItem、setItem、removeItem、clear、length、key、keys、iterate的泛型签名)以及自定义驱动接口LocalForageDriver。typing-tests/localforage-tests.ts中还有针对这些类型的编译期测试。
导入方式取决于 tsconfig 配置:
- 若开启了
allowSyntheticDefaultImports(TypeScript 1.8+ 支持),推荐:
import localForage from "localforage";- 否则使用以下两种之一:
import * as localForage from "localforage"; // 或者,当你的 TypeScript 版本不支持对 UMD 模块使用 ES6 风格导入时: import localForage = require("localforage");自定义驱动:defineDriver
如果你需要接入新的存储后端(比如 Cordova 插件或其他第三方存储),localForage 提供了defineDriver()注册机制。注册后的驱动对所有 localForage 实例全局共享(源码中的DefinedDrivers容器)。
自定义驱动需要实现一组方法,包括_initStorage与核心数据方法:getItem、setItem、removeItem、clear、length、key、keys、iterate(以及可选的dropInstance),并且要提供_driver名称与_support支持检测。defineDriver内部(src/localforage.js)会对驱动做合规校验:
_driver必须存在且不与内置驱动重名;- 必需方法缺失或不是函数时,拒绝注册并抛出
'Custom driver not compliant; see ...#definedriver'错误; - 可选方法(如
dropInstance)未实现时,会自动注入一个抛错"Method ... is not implemented by the current driver"的占位实现; _support既可以是布尔值,也可以是返回 Promise 的异步检测函数。
测试目录中的 test/dummyStorageDriver.js 就是一个可直接参考的"哑驱动"实现,配合 test.customdriver.js 可以学习驱动注册与使用的最小完整流程。仓库还内置了dropInstance的完整实现示例(见 examples/dropInstance.html)。
框架生态
社区为多个框架提供了基于 localForage 的存储适配层,可以让框架的模型/状态直接落地到离线存储:AngularJS(angular-localForage)、Angular 4+(ngforage)、Backbone(localForage-backbone)、Ember(ember-localforage-adapter)、Vue(vlf)以及 NuxtJS(localforage-module)。如需将自己的驱动加入该列表,可以在仓库 issues 中提交。
本地开发与测试
本地开发需要 node/npm 与 bower。准备工作:
# 全局安装 bower(如未安装): npm install -g bower # 克隆并安装依赖: git clone git@github.com:USERNAME/localForage.git cd localForage npm install bower install注意:省略 bower 依赖会导致测试失败!因为 localForage 面向浏览器设计,测试用例显式依赖浏览器环境。
运行测试:
npm test # 或直接运行: grunt test本地测试运行在无头 WebKit(PhantomJS)上,因此需要先安装 PhantomJS;代码同时必须通过 linter(jshint)检查。提交 Pull Request 时,CI(Travis + Sauce Labs)会对 localForage 支持的所有浏览器跑一遍测试矩阵。仓库的测试体系覆盖了:核心 API(test/test.api.js,其中包含iterate、dropInstance等方法的用例)、数据类型(test/test.datatypes.js)、驱动选择(test/test.drivers.js)、配置(test/test.config.js)、多实例、Service Workers 与 Web Workers(test/test.serviceworkers.js、test/test.webworkers.js)等场景。
体积与许可
从 1.7.3 版本起,localForage 的打包体积非常可控,使用 gzip 压缩后为应用增加不到 10KB 的负载:
| 形态 | 体积 |
|---|---|
| minified | ~29kB |
| gzipped | ~8.8kB |
| brotli | ~7.8kB |
localForage 以 Apache License 2.0 协议分发(见仓库 LICENSE),版权归 Mozilla(2013-2016)及其贡献者所有。若你的项目需要离线优先、跨后端一致的键值存储方案,localForage 提供的这套"简单 API + 自动回退 + 任意类型支持"的组合,是值得优先评估的落地方案。
- 前端
- 缓存抽象
【免费下载链接】localForage
💾 Offline storage, improved. Wraps IndexedDB, WebSQL, or localStorage using a simple but powerful API.
相关推荐
localForage API 完全指南:用一套 Promise 风格 API 打通 IndexedDB、WebSQL 与 localStorage
localForage API 完全指南:用一套 Promise 风格 API 打通 IndexedDB、WebSQL 与 localStorage local
前端缓存抽象7-Zip-zstd实战指南:现代压缩算法的终极解决方案
7 Zip zstd实战指南:现代压缩算法的终极解决方案 你是否还在为传统压缩工具的缓慢速度而烦恼?7 Zip zstd正是为你量身定制的解决方案!这个强大的开
桌面应用CLIlocalForage与localStorage:异步存储的终极对比指南
localForage与localStorage:异步存储的终极对比指南 localForage是一款快速简单的JavaScript存储库,通过使用异步存储(I
前端缓存抽象
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考