dexie、idb-keyval 和 localforage 都是用于在浏览器中持久化存储数据的 JavaScript 库,它们都基于 IndexedDB,但抽象层级和功能复杂度各不相同。dexie 是一个功能完整的 IndexedDB 封装库,提供类 SQL 的查询语法和事务支持;idb-keyval 是一个极简的键值对存储工具,仅暴露四个核心方法(get/set/delete/clear);localforage 则兼容 localStorage API,同时底层使用 IndexedDB(或 WebSQL/IndexedDB 回退),适合从 localStorage 迁移的项目。三者均适用于离线优先、PWA 或需要大量结构化数据缓存的场景,但在 API 设计、功能范围和开发体验上有显著差异。
在现代 Web 应用中,客户端存储早已超越简单的 localStorage。当需要存储大量结构化数据、支持离线操作或提升性能时,IndexedDB 成为首选底层技术。然而,原生 IndexedDB API 繁琐且易错。dexie、idb-keyval 和 localforage 正是为简化这一过程而生,但它们的定位和能力截然不同。本文将从真实开发场景出发,深入比较三者的实现方式、适用边界和工程权衡。
idb-keyval 提供最简化的键值对模型 —— 没有表、没有索引、没有模式。所有数据扁平存储,通过字符串键访问任意可序列化值。
// idb-keyval: 极简键值存储
import { get, set, del, clear } from 'idb-keyval';
await set('user-preferences', { theme: 'dark', lang: 'zh' });
const prefs = await get('user-preferences');
await del('user-preferences');
await clear(); // 清空所有
localforage 同样采用键值对模型,但 API 完全模仿 localStorage,便于迁移。它支持回调和 Promise 两种风格。
// localforage: localStorage 风格的异步存储
import localforage from 'localforage';
await localforage.setItem('user-profile', { name: '张三', id: 123 });
const profile = await localforage.getItem('user-profile');
await localforage.removeItem('user-profile');
await localforage.clear();
dexie 则引入完整的数据库概念:表(tables)、索引(indexes)、主键(primary keys)和模式(schema)。你必须先定义数据结构,再进行操作。
// dexie: 声明式数据库模式
import Dexie from 'dexie';
class MyAppDB extends Dexie {
constructor() {
super('MyAppDB');
this.version(1).stores({
friends: '++id, name, age', // 自增 id 为主键,name 和 age 为索引
notes: '++id, title, content'
});
}
}
const db = new MyAppDB();
// 写入
await db.friends.add({ name: '李四', age: 30 });
// 读取
const friend = await db.friends.get(1);
💡 关键区别:
idb-keyval和localforage适合“存/取”场景;dexie适合“管理”数据 —— 当你需要按字段查询、排序或确保数据结构一致性时,后者必不可少。
idb-keyval 不支持任何查询。要找特定数据,你必须知道确切的键名。如果需要按属性查找(如“所有年龄大于 25 的用户”),你只能遍历所有键值对 —— 效率极低。
// idb-keyval: 无法直接查询,需手动遍历(不推荐用于大数据集)
const allKeys = await getAllKeys(); // 需额外实现
for (const key of allKeys) {
const value = await get(key);
if (value?.age > 25) { /* ... */ }
}
localforage 同样缺乏查询能力。它只提供按键获取,不支持条件过滤或排序。
// localforage: 无内置查询
// 必须预先知道键名,如 'user-123'
const user = await localforage.getItem('user-123');
dexie 提供强大的查询 API,支持基于索引的高效查找、范围查询、排序和分页。
// dexie: 高效索引查询
// 查找所有年龄 >= 25 的朋友,按名字排序
const adults = await db.friends
.where('age')
.aboveOrEqual(25)
.sortBy('name');
// 范围查询:ID 在 10 到 20 之间
const recentNotes = await db.notes
.where('id')
.between(10, 20)
.toArray();
💡 工程建议:如果你的数据量超过几百条,或需要按非主键字段检索,
dexie的索引能力可避免 O(n) 扫描,大幅提升性能。
idb-keyval 和 localforage 在内部使用单次事务处理每个操作(如一次 set 或 getItem)。它们不暴露事务 API,因此无法将多个操作组合成原子单元。
// localforage: 无法保证多个操作的原子性
await localforage.setItem('balance', 100);
await localforage.setItem('lastUpdated', Date.now());
// 如果第一个成功、第二个失败,数据会不一致
dexie 显式支持事务,允许你将多个读写操作包裹在同一个事务中,确保 ACID 特性。
// dexie: 显式事务
await db.transaction('rw', db.friends, db.notes, async () => {
await db.friends.add({ name: '王五' });
await db.notes.add({ title: 'Meeting', content: '...' });
// 如果任一操作失败,全部回滚
});
💡 使用场景:金融类应用(如转账)、多表同步更新等需要强一致性的场景,必须使用
dexie的事务机制。
localforage 最注重兼容性。它自动检测浏览器支持情况,并按优先级尝试使用 IndexedDB、WebSQL 或 localStorage 作为后端。这意味着即使在老旧浏览器(如 IE10+)中也能工作。
// localforage: 无需关心底层存储引擎
// 在 Chrome 中用 IndexedDB,在 Safari 旧版中可能用 WebSQL
dexie 和 idb-keyval 仅支持 IndexedDB,因此要求浏览器支持 IndexedDB(现代浏览器基本都支持,但 IE10 需要 polyfill)。它们不提供自动回退到 localStorage 的机制。
💡 决策点:如果你的用户包含大量 IE 用户,
localforage是唯一安全选择;否则,现代项目可放心使用dexie或idb-keyval。
三者均基于 Promise,但错误处理方式略有不同。
idb-keyval:操作失败时 reject Promise,错误类型为 DOMException(如 QuotaExceededError)。localforage:同样 reject Promise,但也支持回调风格(getItem(key, callback))。dexie:提供更丰富的错误类型(如 NotFoundError, ConstraintError),便于针对性处理。// dexie: 特定错误处理
try {
await db.friends.add({ id: 1, name: '重复ID' });
} catch (e) {
if (e instanceof Dexie.ConstraintError) {
console.error('主键冲突');
}
}
idb-keyval(体积最小)或 localforage(若需兼容旧浏览器)。dexie(必须用索引实现高效查询)。localStorage.setItem,希望无缝升级到异步存储。localforage(API 几乎一致,只需替换导入)。dexie(事务支持必不可少)。| 能力 | dexie | idb-keyval | localforage |
|---|---|---|---|
| 数据模型 | 多表 + 索引 + 模式 | 单一扁平键值对 | 单一扁平键值对 |
| 查询能力 | ✅ 高级查询(过滤/排序/范围) | ❌ 仅按键获取 | ❌ 仅按键获取 |
| 事务支持 | ✅ 显式多操作事务 | ❌ 单操作事务 | ❌ 单操作事务 |
| API 风格 | 类 SQL / 链式 | 四个函数(get/set/del/clear) | localStorage 兼容 |
| 浏览器兼容性 | IndexedDB only | IndexedDB only | IndexedDB/WebSQL/localStorage |
| 适用规模 | 大量结构化数据(千条以上) | 少量简单数据(<100 条) | 少量简单数据(<100 条) |
dexie:当你需要数据库级别的能力(查询、索引、事务),不要犹豫。它的学习成本换来的是长期可维护性和性能保障。idb-keyval:如果你只是想找个比 localStorage 更快的异步键值存储,且项目已放弃 IE 支持,它是最轻量的选择。localforage:当你必须支持老旧浏览器,或正在迁移老代码,它的自动回退机制能省去大量兼容性工作。记住:没有“最好”的库,只有“最合适”当前场景的工具。明确你的数据规模、查询需求和兼容性要求,就能做出清晰决策。
选择 localforage 如果你已有基于 localStorage 的代码,希望平滑迁移到更强大的存储后端,同时保留熟悉的 API(setItem/getItem)。它自动处理浏览器兼容性(回退到 WebSQL 或 localStorage),适合需要跨浏览器支持但又不想深入 IndexedDB 细节的项目。不过,它不支持复杂查询或索引,仅适合简单键值场景。
选择 idb-keyval 如果你只需要最简单的键值对存储,且希望零配置、零依赖、极小体积。它非常适合缓存少量配置、用户偏好或临时状态,避免了完整数据库的复杂性。当你不需要查询、索引或事务,只关心快速读写任意 JavaScript 值时,它是轻量级首选。
选择 dexie 如果你需要完整的数据库功能,比如复杂查询(过滤、排序、范围查找)、索引、事务控制或多表关联。它适合构建功能丰富的离线应用(如笔记、待办、CRM 等),能处理成千上万条记录并保持高性能。虽然学习曲线略陡,但其类 SQL 的 API 对熟悉数据库的开发者非常友好。
localForage is a fast and simple storage library for JavaScript. localForage
improves the offline experience of your web app by using asynchronous storage
(IndexedDB or WebSQL) with a simple, localStorage-like API.
localForage uses localStorage in browsers with no IndexedDB or WebSQL support. See the wiki for detailed compatibility info.
To use localForage, just drop a single JavaScript file into your page:
<script src="localforage/dist/localforage.js"></script>
<script>localforage.getItem('something', myCallback);</script>
Try the live example.
Download the latest localForage from GitHub, or install with npm:
npm install localforage
Lost? Need help? Try the localForage API documentation. localForage API文档也有中文版。
If you're having trouble using the library, running the tests, or want to contribute to localForage, please look through the existing issues for your problem first before creating a new one. If you still need help, feel free to file an issue.
Because localForage uses async storage, it has an async API. It's otherwise exactly the same as the localStorage API.
localForage has a dual API that allows you to either use Node-style callbacks or Promises. If you are unsure which one is right for you, it's recommended to use Promises.
Here's an example of the Node-style callback form:
localforage.setItem('key', 'value', function (err) {
// if err is non-null, we got an error
localforage.getItem('key', function (err, value) {
// if err is non-null, we got an error. otherwise, value is the value
});
});
And the Promise form:
localforage.setItem('key', 'value').then(function () {
return localforage.getItem('key');
}).then(function (value) {
// we got our value
}).catch(function (err) {
// we got an error
});
Or, use async/await:
try {
const value = await localforage.getItem('somekey');
// This code runs once the value has been loaded
// from the offline store.
console.log(value);
} catch (err) {
// This code runs if there were any errors.
console.log(err);
}
For more examples, please visit the API docs.
You can store any type in localForage; you aren't limited to strings like in
localStorage. Even if localStorage is your storage backend, localForage
automatically does JSON.parse() and JSON.stringify() when getting/setting
values.
localForage supports storing all native JS objects that can be serialized to JSON, as well as ArrayBuffers, Blobs, and TypedArrays. Check the API docs for a full list of types supported by localForage.
All types are supported in every storage backend, though storage limits in localStorage make storing many large Blobs impossible.
You can set database information with the config() method.
Available options are driver, name, storeName, version, size, and
description.
Example:
localforage.config({
driver : localforage.WEBSQL, // Force WebSQL; same as using setDriver()
name : 'myApp',
version : 1.0,
size : 4980736, // Size of database, in bytes. WebSQL-only for now.
storeName : 'keyvaluepairs', // Should be alphanumeric, with underscores.
description : 'some description'
});
Note: you must call config() before you interact with your data. This
means calling config() before using getItem(), setItem(), removeItem(),
clear(), key(), keys() or length().
You can create multiple instances of localForage that point to different stores
using createInstance. All the configuration options used by
config are supported.
var store = localforage.createInstance({
name: "nameHere"
});
var otherStore = localforage.createInstance({
name: "otherName"
});
// Setting the key on one of these doesn't affect the other.
store.setItem("key", "value");
otherStore.setItem("key", "value2");
You can use localForage with RequireJS:
define(['localforage'], function(localforage) {
// As a callback:
localforage.setItem('mykey', 'myvalue', console.log);
// With a Promise:
localforage.setItem('mykey', 'myvalue').then(console.log);
});
If you have the allowSyntheticDefaultImports compiler option set to true in your tsconfig.json (supported in TypeScript v1.8+), you should use:
import localForage from "localforage";
Otherwise you should use one of the following:
import * as localForage from "localforage";
// or, in case that the typescript version that you are using
// doesn't support ES6 style imports for UMD modules like localForage
import localForage = require("localforage");
If you use a framework listed, there's a localForage storage driver for the models in your framework so you can store data offline with localForage. We have drivers for the following frameworks:
If you have a driver you'd like listed, please open an issue to have it added to this list.
You can create your own driver if you want; see the
defineDriver API docs.
There is a list of custom drivers on the wiki.
You'll need node/npm and bower.
To work on localForage, you should start by
forking it and installing its
dependencies. Replace USERNAME with your GitHub username and run the
following:
# Install bower globally if you don't have it:
npm install -g bower
# Replace USERNAME with your GitHub username:
git clone git@github.com:USERNAME/localForage.git
cd localForage
npm install
bower install
Omitting the bower dependencies will cause the tests to fail!
You need PhantomJS installed to run local tests. Run npm test (or,
directly: grunt test). Your code must also pass the
linter.
localForage is designed to run in the browser, so the tests explicitly require a browser environment. Local tests are run on a headless WebKit (using PhantomJS).
When you submit a pull request, tests will be run against all browsers that localForage supports on Travis CI using Sauce Labs.
As of version 1.7.3 the payload added to your app is rather small. Served using gzip compression, localForage will add less than 10k to your total bundle size:
This program is free software; it is distributed under an Apache License.
Copyright (c) 2013-2016 Mozilla (Contributors).