# localStorage VS IndexedDB

### 基本概念

在Web开发中，本地存储技术是实现离线应用和提高用户体验的关键。`localStorage`和`IndexedDB`是两种常用的浏览器本地存储技术，它们各自有不同的特点和适用场景。

* **localStorage**：是一种简单的键值存储系统，允许你以字符串的形式存储数据。它的主要用途是缓存数据，以便在用户再次访问网页时能够快速加载。`localStorage`适用于小型数据存储，如用户偏好设置、会话信息等。
* **IndexedDB**：是一个更为复杂的NoSQL数据库，它提供了一个非关系型数据库，可以在客户端存储大量结构化数据。`IndexedDB`支持多种数据类型，并且可以通过索引快速查询数据。它适用于需要存储大量数据和复杂数据结构的应用场景，如游戏、地图应用等。

### **TL;DR:**

* **localStorage** 是一个简单的键值存储，适合小型数据存储，如用户设置。并且有最大容量限制 (5M)。
* **IndexedDB** 是一个更复杂的NoSQL数据库，适合存储大量结构化数据，支持异步操作，不会阻塞主线程。
* **Dexie.js** 是一个封装了 IndexedDB 的库，简化了 API，使得操作更加容易和高效。使用 Dexie，可以轻松地进行数据的增删改查操作，并且它支持 Promise 和 async/await。
* 对于需要高效管理和操作大量数据的Web应用，尤其是在离线环境下，使用 IndexedDB 和 Dexie.js 是一个优秀的选择。

### 特性分析

#### 兼容性

localStorage 兼容性

<figure><img src="/files/JZjlyv19cSG26Cn99J8S" alt=""><figcaption></figcaption></figure>

IndexedDB 兼容性

<figure><img src="/files/P0WVdSB9tlkOZNDURDUM" alt=""><figcaption></figcaption></figure>

我们可以看到，在现代浏览器中，主流内核基本已经支持 IndexedDB。

#### 存储容量

* `localStorage`的存储容量受限于浏览器和设备的限制，通常在5MB左右。
* `IndexedDB`的存储容量则大得多，理论上可以存储数GB的数据，具体取决于用户的硬盘空间。

#### 数据类型支持

* `localStorage`仅支持字符串类型的数据。
* `IndexedDB`支持多种数据类型，包括字符串、数字、布尔值、日期等，还可以存储二进制数据和对象。

#### API易用性

* `localStorage` 的 API 简单明了，易于上手。一般只需要使用 getItem、setItem、removeItem 等方法即可。而 IndexedDB 的 API 更加复杂，但也更为强大，它支持数据的增删改查，事务处理，索引创建和查找等操作。
* `IndexedDB`的API较为复杂，但也更为强大。支持数据的增删改查，事务处理，索引创建和查找，对象仓库的操作等，学习曲线较陡。

#### IndexedDB的异步优势

`IndexedDB`的操作是基于异步的，这意味着它不会阻塞主线程，从而不会影响用户界面的响应性。在执行数据库读写操作时，`IndexedDB`会在后台创建一个新的事务线程，这样可以避免长时间的I/O操作阻塞UI线程，确保Web应用的流畅运行。

这种异步性对于Web应用来说非常重要，尤其是对于需要处理大量数据或者复杂计算的应用。例如，一个需要在浏览器端处理大量数据的复杂 Web 应用，如图片编辑器或者大型游戏等，可能需要加载和处理大量的资源和用户数据，这些操作同步进行，可能会导致应用卡顿甚至崩溃，此时就应该选择 IndexedDB 作为你的数据存储方案。同样的，如果正在开发一个 PWA（Progressive Web App） ，它需要在离线状态下也可以访问大量数据，IndexedDB 也会是一个好的选择。因为它既能支持大量的数据存储，又能通过异步操作保证应用的流畅运行。

由于 IndexedDB 操作是异步的，并且默认情况下不会阻塞主线程，因此它通常不会直接影响 TBT。

#### IndexedDB 核心概念

`IndexedDB` 的核心组成部分包括数据库（Database）、对象仓库（Object Store）、索引（Index）和事务（Transaction）。&#x20;

对象仓库是 `IndexedDB` 中存储数据的地方，你可以把它想象成关系数据库中的表。&#x20;

索引是与对象仓库相关联的，它可以让你高效地查询数据。&#x20;

事务用来处理数据库的读写操作，以保证数据库的完整性和一致性。

以下是一个简单的 CRUD 的操作示例

```javascript
// 打开或创建一个 IndexedDB 数据库
let request = window.indexedDB.open("myDatabase", 1);

// 添加数据记录
request.onsuccess = function(event) {
  let db = event.target.result;
  let transaction = db.transaction(["myObjectStore"], "readwrite");
  let objectStore = transaction.objectStore("myObjectStore");
  objectStore.add({ id: 1, name: "my name" });
};

// 读取数据记录
transaction = db.transaction(["myObjectStore"]);
objectStore = transaction.objectStore("myObjectStore");
let request = objectStore.get(1);
request.onsuccess = function(event) {
  console.log("Name: ", request.result.name);
};

// 更新数据记录
transaction = db.transaction(["myObjectStore"], "readwrite");
objectStore = transaction.objectStore("myObjectStore");
request = objectStore.get(1);
request.onsuccess = function(event) {
  request.result.name = "new name";
  objectStore.put(request.result);
};

// 删除数据记录
transaction = db.transaction(["myObjectStore"], "readwrite");
objectStore = transaction.objectStore("myObjectStore");
objectStore.delete(1);

// 使用索引查询数据
transaction = db.transaction(["myObjectStore"]);
objectStore = transaction.objectStore("myObjectStore");
index = objectStore.index("name");
request = index.get("new name");
request.onsuccess = function(event) {
  console.log("ID: ", request.result.id);
};
```

### Dexie: 简便地使用 IndexedDB

即使`IndexedDB` 相比 `localStorage` 有诸多优点，但正是由于其复杂的 API，导致在一般项目中很难看到其身影。这里推荐使用[Dexie](https://dexie.org/)。Dexie.js 是一个处理 IndexedDB 的 JavaScript 库。

```javascript
import Dexie from 'dexie';

const db = new Dexie('MyDatabase');
db.version(1).stores({
    friends: 'name, age' // 定义对象仓库和索引
});

// 添加数据
db.friends.add({
    name: 'John',
    age: 25
});

// 使用索引查询
db.friends.where('age').below(30).each(friend => {
    console.log(friend.name);
});
```

可以看到使用 Dexie.js 让代码更加简洁清晰，更易于理解和维护。

#### Dexie.js 的核心特性

* **易于使用的 API**：Dexie.js 提供了一个更易于使用的 API，隐藏了 IndexedDB 的复杂性，它提供了一个类似于 SQL 数据库的 API，包括表的创建、数据的增删改查等操作。
* **Promise 和 async/await 支持**：Dexie.js 的所有操作都是基于 Promise 的，并且可以与 async/await 配合使用，使得异步代码更加简洁。
* **事务管理**：Dexie.js 提供了对 IndexedDB 事务的封装，使得你可以轻松地管理数据的一致性和完整性。
* **索引和查询**：Dexie.js 支持创建多种类型的索引，包括唯一索引、多值索引和复合索引，以及执行复杂的查询操作。
* **错误处理**：Dexie.js 提供了更加清晰的错误处理机制，使得调试和错误追踪更加容易。
* **性能优化**：Dexie.js 利用了 IndexedDB 的一些高级特性来优化性能，例如批量操作和避免不必要的事件监听。

#### 使用示例

假设我们现在要在一个 web 应用中使用 Dexie 和 IndexedDB。我们可用新建一个 db.ts

{% code title="shared/db.ts" %}

```typescript
import Dexie, { Table } from 'dexie'

interface DBChatItem {
    id?: number
    chatId: string
    role: string
    text: string
    timestamp: number
}

class IndexedDexie extends Dexie {
    chats!: Table<DBChatItem>
    
    constructor(dbname: string) {
        super(dbname)
        this.version(1.0).stores({
            chats: '++id, chatId, role, text, timestamp'
        })
    }
}

export const chatDb = new IndexedDexie('chatDb')
```

{% endcode %}

现在我们有了一个版本号为 1.0，名称为 chatDb 的 IndexedDB

当我们需要使用这个 chatDb 的时候，我们只需要在其他文件中：

```typescript
import { chatDb } from '../shared/db'

// 新增对话记录
function addNewChat (text) {
    chatDb.chats.add({
        chatId: 1,
        role: "user",
        text: text,
        timestamp: Date.now(),
    })
}

// 更新某一条对话记录
async function updateChat (chatId, text) {
    const theChatInDB = await chatDb.chat.get({ chatId })
    if (theChatInDB?.id) {
        await chatDb.chat.update(theChatInDB.id, { text })
    }
}
```

无论是直接使用 IndexedDB 还是通过 Dexie 这样的库来操作，都能够显著提升 Web 应用的性能和用户体验。它们使得 Web 应用能够离线工作，处理大量数据，并提供快速的响应能力。对于那些寻求更高效、更易于管理的数据库操作的开发需求，使用 Dexie 来操作 IndexedDB 是一个合适的方案。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://dev.viku.org/localstorage-vs-indexeddb.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
