localforage vs idb-keyval vs dexie
前端客户端存储库选型:Dexie、idb-keyval 与 localForage 深度对比
localforageidb-keyvaldexie类似的npm包:

前端客户端存储库选型:Dexie、idb-keyval 与 localForage 深度对比

dexieidb-keyvallocalforage 都是用于在浏览器中持久化存储数据的 JavaScript 库,它们都基于 IndexedDB,但抽象层级和功能复杂度各不相同。dexie 是一个功能完整的 IndexedDB 封装库,提供类 SQL 的查询语法和事务支持;idb-keyval 是一个极简的键值对存储工具,仅暴露四个核心方法(get/set/delete/clear);localforage 则兼容 localStorage API,同时底层使用 IndexedDB(或 WebSQL/IndexedDB 回退),适合从 localStorage 迁移的项目。三者均适用于离线优先、PWA 或需要大量结构化数据缓存的场景,但在 API 设计、功能范围和开发体验上有显著差异。

npm下载趋势

3 年

GitHub Stars 排名

统计详情

npm包名称
下载量
Stars
大小
Issues
发布时间
License
localforage5,394,22425,767-2495 年前Apache-2.0
idb-keyval3,305,1403,160928 kB2510 个月前Apache-2.0
dexie922,61814,1243.09 MB5893 个月前Apache-2.0

前端客户端存储库选型:Dexie、idb-keyval 与 localForage 深度对比

在现代 Web 应用中,客户端存储早已超越简单的 localStorage。当需要存储大量结构化数据、支持离线操作或提升性能时,IndexedDB 成为首选底层技术。然而,原生 IndexedDB API 繁琐且易错。dexieidb-keyvallocalforage 正是为简化这一过程而生,但它们的定位和能力截然不同。本文将从真实开发场景出发,深入比较三者的实现方式、适用边界和工程权衡。

🗃️ 数据模型与 API 设计:从键值对到关系型数据库

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-keyvallocalforage 适合“存/取”场景;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-keyvallocalforage 在内部使用单次事务处理每个操作(如一次 setgetItem)。它们不暴露事务 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

dexieidb-keyval 仅支持 IndexedDB,因此要求浏览器支持 IndexedDB(现代浏览器基本都支持,但 IE10 需要 polyfill)。它们不提供自动回退到 localStorage 的机制。

💡 决策点:如果你的用户包含大量 IE 用户,localforage 是唯一安全选择;否则,现代项目可放心使用 dexieidb-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('主键冲突');
  }
}

🧪 实际场景选型指南

场景 1:缓存用户设置(主题、语言等)

  • 需求:存储少量简单对象,无需查询。
  • 推荐idb-keyval(体积最小)或 localforage(若需兼容旧浏览器)。

场景 2:离线笔记应用

  • 需求:存储数百篇笔记,支持按标题/标签搜索、按创建时间排序。
  • 推荐dexie(必须用索引实现高效查询)。

场景 3:从 localStorage 迁移遗留代码

  • 需求:现有代码大量使用 localStorage.setItem,希望无缝升级到异步存储。
  • 推荐localforage(API 几乎一致,只需替换导入)。

场景 4:电商购物车(离线支持)

  • 需求:存储商品列表,支持增删改查,需保证操作原子性(如清空购物车时同时更新优惠券状态)。
  • 推荐dexie(事务支持必不可少)。

📊 总结:核心差异速查表

能力dexieidb-keyvallocalforage
数据模型多表 + 索引 + 模式单一扁平键值对单一扁平键值对
查询能力✅ 高级查询(过滤/排序/范围)❌ 仅按键获取❌ 仅按键获取
事务支持✅ 显式多操作事务❌ 单操作事务❌ 单操作事务
API 风格类 SQL / 链式四个函数(get/set/del/clear)localStorage 兼容
浏览器兼容性IndexedDB onlyIndexedDB onlyIndexedDB/WebSQL/localStorage
适用规模大量结构化数据(千条以上)少量简单数据(<100 条)少量简单数据(<100 条)

💡 最终建议

  • 要功能,选 dexie:当你需要数据库级别的能力(查询、索引、事务),不要犹豫。它的学习成本换来的是长期可维护性和性能保障。
  • 要简单,选 idb-keyval:如果你只是想找个比 localStorage 更快的异步键值存储,且项目已放弃 IE 支持,它是最轻量的选择。
  • 要兼容,选 localforage:当你必须支持老旧浏览器,或正在迁移老代码,它的自动回退机制能省去大量兼容性工作。

记住:没有“最好”的库,只有“最合适”当前场景的工具。明确你的数据规模、查询需求和兼容性要求,就能做出清晰决策。

如何选择: localforage vs idb-keyval vs dexie

  • localforage:

    选择 localforage 如果你已有基于 localStorage 的代码,希望平滑迁移到更强大的存储后端,同时保留熟悉的 API(setItem/getItem)。它自动处理浏览器兼容性(回退到 WebSQL 或 localStorage),适合需要跨浏览器支持但又不想深入 IndexedDB 细节的项目。不过,它不支持复杂查询或索引,仅适合简单键值场景。

  • idb-keyval:

    选择 idb-keyval 如果你只需要最简单的键值对存储,且希望零配置、零依赖、极小体积。它非常适合缓存少量配置、用户偏好或临时状态,避免了完整数据库的复杂性。当你不需要查询、索引或事务,只关心快速读写任意 JavaScript 值时,它是轻量级首选。

  • dexie:

    选择 dexie 如果你需要完整的数据库功能,比如复杂查询(过滤、排序、范围查找)、索引、事务控制或多表关联。它适合构建功能丰富的离线应用(如笔记、待办、CRM 等),能处理成千上万条记录并保持高性能。虽然学习曲线略陡,但其类 SQL 的 API 对熟悉数据库的开发者非常友好。

localforage的README

localForage

Build Status NPM version Dependency Status npm jsDelivr Hits minzipped size

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

Support

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.

How to use localForage

Callbacks vs Promises

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.

Storing Blobs, TypedArrays, and other JS objects

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.

Configuration

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().

Multiple instances

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");

RequireJS

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);
});

TypeScript

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");

Framework Support

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.

Custom Drivers

You can create your own driver if you want; see the defineDriver API docs.

There is a list of custom drivers on the wiki.

Working on localForage

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!

Running Tests

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.

Library Size

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:

minified
`~29kB`
gzipped
`~8.8kB`
brotli'd
`~7.8kB`

License

This program is free software; it is distributed under an Apache License.


Copyright (c) 2013-2016 Mozilla (Contributors).