原文链接 IndexedDB for Client-Side Storage - 作者 Omri Luz
客户端存储 IndexedDB:权威指南
IndexedDB
是很强大的低级 API
,用户客户端存储大量结构化数据,包括文件或者blob
。不像其他的 web
存储技术,IndexedDB
允许更加复杂的搜索,关联关系,索引,为开发者提供强有力的能力,开发健壮的应用。本文将深入探索 IndexedDB
历史背景、技术复杂性、实际应用、性能考虑和真实场景应用。
历史背景
Web 存储的进化
在介绍 IndexedDB
之前,WEB
开发者原先是依赖 COOKIES
和 LOCALSTORAGE
来做客户端的数据存储。Cookies
使用小数量的数据(通常是 4kb
),面临的是大小和性能的限制。LocalStorage
是 HTML5
引入进来的,允许存储更大的数据(通常是 5MB
),但是缺少复杂查询的能力。
随着 WEB
应用的进化,对更复杂的客户端存储方案需求显而易见,尤其是随着单页面应用(SPAs)
和对离线功能的需要。
IndexedDB 的发展
IndexedDB
的设计之初是为了填补现有技术留下的空白。它在 2010
年成为提案,在 2015
年被官方 W3C
推荐。它允许开发者在客户端存储重要的海量数据结构数据,支持复杂的数据类型,比如数组和对象。
IndexedDB
更新的时间线:
- 2010:初始提案并起草
- 2012 :在主流浏览器(比如
Chrome
,Firefox
)上进行基本应用 - 2015 :成为
W3C
推荐 - 2019:性能提升和更好的错误处理
技术基础
基本概念
Key-Value 保存
IndexedDB
充当键值存储,其中数据以对象的形式存储。每个对象通过一个 key
来定义,这个键可以是一个字符串或者更复杂的键(比如字符串和数字的联合)。
对象存储
IndexedDB
中的数据被分组到对象存储中。每个对象存储可以容纳不同类型的数据结构,从而实现有序的存储和检索。对象存储可以包含简单的数据类型、数组,甚至更复杂的对象。
索引 Indexes
IndexedDB
中的 Indexes
索引提供了一种检索记录更有效的方法。使用一个索引,我们可以根据主键之外的属性查看条目,这可以大大提高读取密集型应用程序的性能。
事务 Transactions
与 IndexedDB
数据库的所有交互,都需要在事务上下文中执行,从而保证数据完整性。事务可以是只读的,也可以是只写的。
核心 API 结构
IndexedDB
主要围绕 indexedDB
对象展开,该对象是所有数据库操作的入口点。
打开一个数据库
javascript
const request = indexedDB.open("myDatabase", 1);
request.onerror = (event) => {
console.error("Database error:", error);
};
request.onsuccess = (event) => {
const db = event.target.result;
console.log("Database opened successfully:", db);
};
request.onupgradedneeded = (event) => {
const db = event.target.result;
const store = db.createObjectStore("myStore", { keyPath: "id" });
store.createdIndex("nameIndex", "name", { unique: false });
console.log("Object store and index created");
}
在上面的例子中,我们初始化了一个数据库,然后创建一个 myStore
对象存储,带有 ID
的关键路径,一个名为 nameIndex
的索引,能够基于 name
属性有效地查询。
完整地代码案例
IndexedDB 中地 CRUD
javacript
// 添加数据
const addData = (db, data) => {
const transaction = db.transaction(["myStore"], "readwrite");
const store = transaction.objectStore("myStore");
const request = store.add(data);
request.onsuccess = () => {
console.log("Data added: ", data);
};
request.onerror = () => {
console.error("Error adding data: ", request.error);
}
}
// 拉取数据
const fetchData = (db, id) => {
const transaction = db.transaction(["myStore"], "readonly");
const store = transaction.objectStore("myStore");
const request = store.get(id);
request.onsuccess = () => {
console.log("Data fetched: ", request.result);
}
request.onerror = () => {
console.error("Error fetching data: ", request.error);
}
}
// 更新数据
const updateData = (db, updatedData) => {
const transaction = db.transaction(["myStore"], "readwrite");
const store = transaction.objectStore("myStore");
const request = store.put(updatedData);
request.onsuccess = () => {
console.log("Data updated: ", updatedData);
}
request.onerror = () => {
console.error("Error updating data: ", request.error);
}
}
// 删除数据
const deleteData = (db, id) => {
const transaction = db.transaction(["myStore"], "readwrite");
const store = transaction.objectStore("myStore");
const request = store.delete(id);
request.onsuccess = () => {
console.log("Data deleted: ", id);
}
request.onerror = () => {
console.error("Error deleting data: ", request.error);
}
}
高级应用:处理复杂地数据关系
考虑这么一个场景:我们需要管理一个博文应用,每个博文后面都有很多评论。使用 IndexedDB
,我们可以实现更复杂的数据结构,以便我们高效查询和关联。
javascript
// 案例 Schema:发布和评论
const schemaPost = {
id: 1,
title: "IndexedDB Guide",
content: "This is an extensive guide on using IndexedDB.",
comments: [
{ commentId: 1, text: "Great article!", timestamp: Date.now() },
{ commentId: 2, text: "Very informative.", timestamp: Date.now() }
]
};
const addPost = (db, post) => {
const transaction = db.transaction(["posts"], "readwrite");
const store = tansaction.objectStore("posts");
const request = store.put(post);
request.onsuccess = () => {
console.log("Post added/updated: ", post);
};
request.onerror = () => {
console.error("Error adding/updating post: ", request.error);
};
};
const fetchComments = (db, postId) => {
const transaction = db.transaction(["posts"], "readonly");
const store = transaction.objectStore("posts");
const request = store.get(postId
request.onsuccess = () => {
const post = request.result;
console.log("Comments: ", post.comments);
}
request.onerror = () => {
console.error("Error fetching post comments: ", request.error);
};
};
使用 Indexes 高级查询
通过使用索引,我们可以给用户提供获取博文和评论的能力,其基于 ID
以外。这个额外的索引随着应用的迭代,可以提升性能。
javascript
const fetchCommentsByKeyword = (db, keyword) => {
const transaction = db.transaction(["posts"], "readonly");
const store = transaction.objectStore("posts");
const index = store.index("commentsIndex");
const request = index.getAll(keyword);
request.onsuccess = () => {
console.log("Comments fetched by keyword: ", request.result)
};
request.onerror = () => {
console.error("Error fetching comments by keyword: ", request.error);
};
};
边缘案例和高级实现技术
-
处理大数据集合 :在处理大数据的时候,一个常见的陷阱是浏览器的性能。对于这些情况,请考虑从
IndexedDB
实例检索记录时,使用批量事务或者分页策略。 -
版本控制数据结构 :当应用程序的数据模式发生变化时,正确处理数据库的版本很重要。请确保在
onupgradeneeded
事件中提供迁移的脚本。 -
错误处理 :在不同的层级(
open, read, write
)有效处理错误,能够确保一个流畅的用户体验和更容易调试。创建一个模式来一致记录错误信息。
性能考虑和优化策略
-
限制事务范围:保证事务简短,并且在小范围管理数据,以提高速度。
-
批量操作:在单个事务中对多个操作进行分组以减少开销。
-
明智使用索引:为那些频繁操作的字段定义索引,以便提升查找性能。然而,需要保持平衡,因为过多的索引会减慢插入的速度。
-
数据库大小管理 :利用
navigator.storage.estimate()
来观测存储的用量,如果存储空间接近存储能力,则给用户一个提醒。
真实世界用户案例
-
离线应用 :比如像
Google Docs
应用那样,使用IndexedDB
允许用户在断网的条件下使用。当网络恢复后同步数据。 -
渐进网页应用(PWAs) :
PWAs
通常使用IndexedDB
来缓存assets
, 用户的偏好,和提成离线用户体验的数据。 -
E-commerce Applications :应用程序可以使用
IndexedDB
在本地存储用户的购物车商品,无论连接如何变化都可以提供无缝体验。 -
游戏应用 :许多基于
web
的游戏利用IndexedDB
来保存玩家的进度和游戏的状态,启用持久会话。
调试技术
-
Browser DevTools :在
DevTools
中使用"应用程序"选项卡来监测数据库、对象存储和事务。我们可以检索条目,查看索引并执行手动查询。 -
Error Logging:错误日志来捕获异常。使用集中式日志记录机制存储这些日志,方便后面分析。
-
单元测试:创建功能性的自动化测试,比如应用中数据输入,获取和错误处理。
总结
IndexedDB
是一款强大且灵活的客户端存储解决方案。从复杂的数据关系和索引,到优化和调试策略,有效利用 IndexedDB
需要我们了解它的架构和 API
。
随着浏览器能力的提升和使用模式的发展,掌握 IndexedDB
对于开发现代 WEB
应用的开发者来说至关重要。更多的内容,可参考官网 official IndexedDB documentation 和其他资源,比如 IndexedDB for Beginners 和 Web Storage API comparison。
通过利用 IndexedDB
的复杂性和强大功能,开发者可以创建出引人入胜的应用,即使在网络不稳定的情况下,也能提供丰富的数据体验,为未来的 WEB
发展奠定基础。