Web 开发者已经可以使用 localStorage 来进行简单的键值对存储。但对于许多需要结构化存储和索引数据的 Web 应用来说,这还不够。Mozilla 正在开发一个名为 IndexedDB 的结构化存储 API,它支持索引。我们将在未来几周内发布一些测试版本。这可以与 WebDatabase API 相比较,WebDatabase API 由多个浏览器实现,它使用 SQLite 的一个子集。Mozilla 选择不为 多种原因,这篇文章对此进行了讨论 实现 WebDatabase。
为了比较 IndexedDB 和 WebDatabase,我们将展示四个示例,这些示例使用每个规范中大部分异步 API。阅读这些示例后,SQL 存储使用表格(WebDatabase)和 JavaScript 对象存储使用索引(IndexedDB)之间的差异会变得很明显。这些 API 的同步版本仅在 worker 线程上可用。由于并非所有浏览器都实现了 worker 线程,因此此时不会讨论同步 API。IndexedDB 代码基于 Mozilla 提交给 W3C WebApps 工作组的提案,该提案目前已获得积极反馈。这两个 API 的代码不包含任何错误处理(为了简洁),但生产代码应始终包含错误处理!
这些示例用于存储糖果店销售糖果给顾客(我们称之为孩子)的情况。candySales
中的每个条目代表向一个孩子销售一定数量的糖果,该孩子由 candy
和 kids
中的相应条目指定。
示例 1 - 打开和设置数据库
第一个示例演示了如何打开数据库连接以及在版本号不正确的情况下创建表格或对象存储。打开数据库后,两个示例都会检查版本并创建必要的表格或对象存储,然后设置正确的版本号。WebDatabase 在处理版本方面更加严格,如果数据库版本不是调用者期望的版本(由传入 openDatabase
的第二个参数指定),它会抛出错误。IndexedDB 允许调用者根据需要处理版本控制。请注意,工作组正在积极讨论 IndexedDB 如何处理版本更改。
WebDatabase
var db = window.openDatabase("CandyDB", "",
"My candy store database",
1024);
if (db.version != "1") {
db.changeVersion(db.version, "1", function(tx) {
// User's first visit. Initialize database.
var tables = [
{ name: "kids", columns: ["id INTEGER PRIMARY KEY",
"name TEXT"]},
{ name: "candy", columns: ["id INTEGER PRIMARY KEY",
"name TEXT"]},
{ name: "candySales", columns: ["kidId INTEGER",
"candyId INTEGER",
"date TEXT"]}
];
for (var index = 0; index < tables.length; index++) {
var table = tables[index];
tx.executeSql("CREATE TABLE " + table.name + "(" +
table.columns.join(", ") + ");");
}
}, null, function() { loadData(db); });
}
else {
// User has been here before, no initialization required.
loadData(db);
}
IndexedDB
var request = window.indexedDB.open("CandyDB",
"My candy store database");
request.onsuccess = function(event) {
var db = event.result;
if (db.version != "1") {
// User's first visit, initialize database.
var createdObjectStoreCount = 0;
var objectStores = [
{ name: "kids", keyPath: "id", autoIncrement: true },
{ name: "candy", keyPath: "id", autoIncrement: true },
{ name: "candySales", keyPath: "", autoIncrement: true }
];
function objectStoreCreated(event) {
if (++createdObjectStoreCount == objectStores.length) {
db.setVersion("1").onsuccess = function(event) {
loadData(db);
};
}
}
for (var index = 0; index < objectStores.length; index++) {
var params = objectStores[index];
request = db.createObjectStore(params.name, params.keyPath,
params.autoIncrement);
request.onsuccess = objectStoreCreated;
}
}
else {
// User has been here before, no initialization required.
loadData(db);
}
};
示例 2 - 将孩子存储到数据库
此示例将多个孩子存储到相应的表格或对象存储中。此示例演示了使用 WebDatabase 时必须处理的一种风险:SQL 注入攻击。在 WebDatabase 中,必须使用显式事务,但在 IndexedDB 中,如果只访问一个对象存储,则会自动提供事务。IndexedDB 中的事务锁定是针对每个对象存储的。此外,IndexedDB 使用 JavaScript 对象进行插入,而 WebDatabase 要求调用者绑定特定的列。在这两种情况下,您都会在回调函数中获得插入 ID。
WebDatabase
var kids = [
{ name: "Anna" },
{ name: "Betty" },
{ name: "Christine" }
];
var db = window.openDatabase("CandyDB", "1",
"My candy store database",
1024);
db.transaction(function(tx) {
for (var index = 0; index < kids.length; index++) {
var kid = kids[index];
tx.executeSql("INSERT INTO kids (name) VALUES (:name);", [kid],
function(tx, results) {
document.getElementById("display").textContent =
"Saved record for " + kid.name +
" with id " + results.insertId;
});
}
});
IndexedDB
var kids = [
{ name: "Anna" },
{ name: "Betty" },
{ name: "Christine" }
];
var request = window.indexedDB.open("CandyDB",
"My candy store database");
request.onsuccess = function(event) {
var objectStore = event.result.objectStore("kids");
for (var index = 0; index < kids.length; index++) {
var kid = kids[index];
objectStore.add(kid).onsuccess = function(event) {
document.getElementById("display").textContent =
"Saved record for " + kid.name + " with id " + event.result;
};
}
};
示例 3 - 列出所有孩子
此示例列出了存储在 kids
表或 kids
对象存储中的所有孩子。WebDatabase 使用结果集对象,该对象将在所有行都检索完成后传递给提供的回调方法。另一方面,IndexedDB 在检索到结果时会将游标传递给事件处理程序。因此,结果应该返回得更快。虽然本示例中没有展示,但您也可以通过简单地不调用 cursor.continue()
来停止使用 IndexedDB 迭代数据。
WebDatabase
var db = window.openDatabase("CandyDB", "1",
"My candy store database",
1024);
db.readTransaction(function(tx) {
// Enumerate the entire table.
tx.executeSql("SELECT * FROM kids", function(tx, results) {
var rows = results.rows;
for (var index = 0; index < rows.length; index++) {
var item = rows.item(index);
var element = document.createElement("div");
element.textContent = item.name;
document.getElementById("kidList").appendChild(element);
}
});
});
IndexedDB
var request = window.indexedDB.open("CandyDB",
"My candy store database");
request.onsuccess = function(event) {
// Enumerate the entire object store.
request = event.result.objectStore("kids").openCursor();
request.onsuccess = function(event) {
var cursor = event.result;
// If cursor is null then we've completed the enumeration.
if (!cursor) {
return;
}
var element = document.createElement("div");
element.textContent = cursor.value.name;
document.getElementById("kidList").appendChild(element);
cursor.continue();
};
};
示例 4 - 列出购买过糖果的孩子
此示例列出了所有孩子以及每个孩子购买了多少糖果。WebDatabase 只需使用 LEFT JOIN 查询,使此示例非常简单。IndexedDB 目前没有为在不同对象存储之间执行连接指定的 API。因此,此示例打开一个指向 kids
对象存储的游标和一个指向 candySales
对象存储上的 kidId
索引的对象游标,并手动执行连接。
WebDatabase
var db = window.openDatabase("CandyDB", "1",
"My candy store database",
1024);
db.readTransaction(function(tx) {
tx.executeSql("SELECT name, COUNT(candySales.kidId) " +
"FROM kids " +
"LEFT JOIN candySales " +
"ON kids.id = candySales.kidId " +
"GROUP BY kids.id;",
function(tx, results) {
var display = document.getElementById("purchaseList");
var rows = results.rows;
for (var index = 0; index < rows.length; index++) {
var item = rows.item(index);
display.textContent += ", " + item.name + "bought " +
item.count + "pieces";
}
});
});
IndexedDB
candyEaters = [];
function displayCandyEaters(event) {
var display = document.getElementById("purchaseList");
for (var i in candyEaters) {
display.textContent += ", " + candyEaters[i].name + "bought " +
candyEaters[i].count + "pieces";
}
};
var request = window.indexedDB.open("CandyDB",
"My candy store database");
request.onsuccess = function(event) {
var db = event.result;
var transaction = db.transaction(["kids", "candySales"]);
transaction.oncomplete = displayCandyEaters;
var kidCursor;
var saleCursor;
var salesLoaded = false;
var count;
var kidsStore = transaction.objectStore("kids");
kidsStore.openCursor().onsuccess = function(event) {
kidCursor = event.result;
count = 0;
attemptWalk();
}
var salesStore = transaction.objectStore("candySales");
var kidIndex = salesStore.index("kidId");
kidIndex.openObjectCursor().onsuccess = function(event) {
saleCursor = event.result;
salesLoaded = true;
attemptWalk();
}
function attemptWalk() {
if (!kidCursor || !salesLoaded)
return;
if (saleCursor && kidCursor.value.id == saleCursor.kidId) {
count++;
saleCursor.continue();
}
else {
candyEaters.push({ name: kidCursor.value.name, count: count });
kidCursor.continue();
}
}
}
总的来说,IndexedDB 简化了与数据库交互的编程模型,并允许使用大量用例。工作组正在设计这个 API,以便它可以被 JavaScript 库包装;例如,在我们的 IndexedDB 实现之上有很多空间可以构建 CouchDB 样式的 API。在 IndexedDB 之上构建基于 SQL 的 API(例如 WebDatabase)也是非常可能的。Mozilla 渴望获得开发人员对 IndexedDB 的反馈,尤其是因为规范尚未最终确定。欢迎在这里留下评论,表达您的想法,或通过 Rypple 留下匿名反馈。
182 评论