大家有HTML5 IndexDB的Demo吗,要测试通过的,最近学这个时写的代码老是出错
大体流程是这样
1.打开数据库
var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;
if ('webkitIndexedDB' in window) {
window.IDBTransaction = window.webkitIDBTransaction;
window.IDBKeyRange = window.webkitIDBKeyRange;
}
//这个就不解释了
var request = indexedDB.open("adsageIDB"); //open : indexedDB只有这一个方法 打开(数据库名)
request.onsuccess = function(e) { //异步
var v = "1.00";
var db = e.target.result;
if (v!= db.version) {
var setVrequest = db.setVersion(v);
setVrequest.onsuccess = function(e) { //异步
if(db.objectStoreNames.contains("todo")) {
db.deleteObjectStore("todo");
}
var store = db.createObjectStore("todo", {keyPath: "adsid"});//onsuccess 后创建ObjectStore 暂时用到两个参数,数据库&&主键
}
}
}
这样就 创建/连接 了一个数据库2.创建交互对象 && 监听dom事件 && 处理数据然后就是要操作数据库了
//插入数据 暂时只插入一列
var trans = db.transaction(["todo"], IDBTransaction.READ_WRITE);//创建transaction
var store = trans.objectStore("todo");//创建Store
//要操作数据必须建立transaction 和 Store
var data = {
"text": todoText,
"adsid": new Date().getTime()
};//一个小数据 adsid是主键
var request = store.put(data); //‘强行’插入
request.onsuccess = function(e) {
//成功后执行一些操作
};
request.onerror = function(e) {
console.log("Error Adding: ", e);
};
//读取数据
var trans = db.transaction(["todo"], IDBTransaction.READ_WRITE);
var store = trans.objectStore("todo");
var keyRange = IDBKeyRange.lowerBound(0);
var cursorRequest = store.openCursor(keyRange);
//这里用到指针cursor ,openCursor的参数 keyRange是遍历范围 还可以添加遍历方向参数
//另一种方法是get() 这个就比较简单了直接store.get('键值')就行
cursorRequest.onsuccess = function(e) {
var result = e.target.result;
if(!!result == false)
return;
console.log(result.value);
result.continue(); //循环读取所有数据
};
//删除数据
...
store.delete('键值')
...
出了一个小demo
<!DOCTYPE html>
<html>
<head>
<script>
var indexedDB = window.indexedDB || window.webkitIndexedDB ||
window.mozIndexedDB;
if ('webkitIndexedDB' in window) {
window.IDBTransaction = window.webkitIDBTransaction;
window.IDBKeyRange = window.webkitIDBKeyRange;
}
adsageIDB = {};
adsageIDB.db = null;
adsageIDB.onerror = function(e) {
console.log(e);
};
adsageIDB.open = function() {
var request = indexedDB.open("adsageIDB");
request.onsuccess = function(e) {
var v = "1.00";
adsageIDB.db = e.target.result;
var db = adsageIDB.db;
if (v!= db.version) {
var setVrequest = db.setVersion(v);
setVrequest.onerror = adsageIDB.onerror;
setVrequest.onsuccess = function(e) {
if(db.objectStoreNames.contains("todo")) {
db.deleteObjectStore("todo");
}
var store = db.createObjectStore("todo",
{keyPath: "adsid"});
adsageIDB.getAllTodoItems();
};
}
else {
adsageIDB.getAllTodoItems();
}
};
request.onerror = adsageIDB.onerror;
}
adsageIDB.addTodo = function(todoText) {
var db = adsageIDB.db;
var trans = db.transaction(["todo"], IDBTransaction.READ_WRITE);
var store = trans.objectStore("todo");
var data = {
"text": todoText,
"adsid": new Date().getTime()
};
var request = store.put(data);
request.onsuccess = function(e) {
adsageIDB.getAllTodoItems();
};
request.onerror = function(e) {
console.log("Error Adding: ", e);
};
};
adsageIDB.deleteTodo = function(id) {
var db = adsageIDB.db;
var trans = db.transaction(["todo"], IDBTransaction.READ_WRITE);
var store = trans.objectStore("todo");
var request = store.delete(id);
request.onsuccess = function(e) {
adsageIDB.getAllTodoItems();
};
request.onerror = function(e) {
console.log("Error Adding: ", e);
};
};
adsageIDB.getAllTodoItems = function() {
var todos = document.getElementById("todoItems");
todos.innerHTML = "";
var db = adsageIDB.db;
var trans = db.transaction(["todo"], IDBTransaction.READ_WRITE);
var store = trans.objectStore("todo");
var keyRange = IDBKeyRange.lowerBound(0);
var cursorRequest = store.openCursor(keyRange);
cursorRequest.onsuccess = function(e) {
var result = e.target.result;
if(!!result == false)
return;
renderTodo(result.value);
result.continue();
};
cursorRequest.onerror = adsageIDB.onerror;
};
function renderTodo(row) {
var todos = document.getElementById("todoItems");
var li = document.createElement("li");
var a = document.createElement("a");
var t = document.createTextNode(row.text);
a.addEventListener("click", function() {
adsageIDB.deleteTodo(row.adsid);
}, false);
a.textContent = " [删除]";
li.appendChild(t);
li.appendChild(a);
todos.appendChild(li)
}
function addTodo() {
var todo = document.getElementById("todo");
adsageIDB.addTodo(todo.value);
todo.value = "";
}
function init() {
adsageIDB.open();
}
window.addEventListener("DOMContentLoaded", init, false);
</script>
</head>
<body>
<ul id="todoItems"></ul>
<input type="text" id="todo" name="todo" placeholder="adsageIDB text?" />
<input type="submit" value="增加一个 IDB" onclick="addTodo(); return false;"/>
</body>
</html>
向数据库中增加数据
如果你刚刚创建了一个数据库,你可能想往里面写点东西。看起来会像下面这样:
var transaction = db.transaction(["customers"], "readwrite");
// 注意: 旧版实验性的实现使用不建议使用的常量 IDBTransaction.READ_WRITE 而不是 "readwrite"。
// 如果你想支持这样的实现,你只要这样写就可以了:
// var transaction = db.transaction(["customers"], IDBTransaction.READ_WRITE);
transaction()
方法接受三个参数(虽然两个是可选的)并返回一个事务对象。第一个参数是事务希望跨越的对象存储空间的列表。如果你希望事务能够跨越所有的对象存储空间你可以传入一个空数组。如果你没有为第二个参数指定任何内容,你得到的是只读事务。因为这里我们是想要写入所以我们需要传入"readwrite"
标识。
现在我们已经有了一个事务,我们需要理解它的生命周期。事务和事件循环的联系非常密切。如果你创建了一个事务但是并没有使用它就返回给事件循环,那么事务将变得无效。保持事务活跃的唯一方法就是在其上构建一个请求。当请求完成时你将会得到一个 DOM 事件,并且,假设请求成功了,你将会有另外一个机会在回调中来延长这个事务。如果你没有延长事务就返回到了事件循环,那么事务将会变得不活跃,依此类推。只要还有待处理的请求事务就会保持活跃。事务生命周期真的很简单但是可能需要一点时间你才能对它变得习惯。还有就是来几个例子也会有所帮助。如果你开始看到TRANSACTION_INACTIVE_ERR
错误代码,那么你已经把某些事情搞乱了。
事务可以接收三种不同类型的 DOM 事件: error
,abort
,以及 complete
。我们已经讨论过error
事件冒泡,所以一个事务要接收所有可能产生错误事件的请求所产生的错误事件。更微妙的一点是一个 error 的默认行为是终止发生错误的事务。除非你在 error 事件上通过调用preventDefault()
处理了这个错误,整个事务被回滚了。这样的设计迫使你去思考和处理错误,但是如果细粒度的错误处理太过繁琐的话,你也可以总是对数据库添加一个总的错误处理程序。如果你不处理一个错误事件或者你在事务中调用abort()
,那么事务被回滚并且有关事物的一个 abort
事件被触发。否则,在所有的未处理请求都完成后,你将得到一个complete
事件。如果你正在做大量的数据库操作,那么追踪事务而不是单个的请求当然可以帮助你进行决断。
现在你有了一个事务了,你将需要从它拿到一个对象存储空间。事务只能让你拿到一个你在创建事务时已经指定过的对象存储空间。然后你可以增加所有你需要的数据。
现在你有了一个事务了,你将需要从它拿到一个对象存储空间。事务只能让你拿到一个你在创建事务时已经指定过的对象存储空间。然后你可以增加你需要的所有数据。
// 当所有的数据都被增加到数据库时执行一些操作
transaction.oncomplete = function(event) {
alert("All done!");
};
transaction.onerror = function(event) {
// 不要忘记进行错误处理!
};
var objectStore = transaction.objectStore("customers");
for (var i in customerData) {
var request = objectStore.add(customerData[i]);
request.onsuccess = function(event) {
// event.target.result == customerData[i].ssn
};
}
产生自 add()
调用的请求的 result
是被添加的值的键。因此在这种情况下,它应该等于被添加的对象的ssn
属性, 因为对象存储空间使用 ssn
属性作为 key path。 注意 add()
函数要求数据库中不能已经有相同键的对象存在。如果你正在试图修改一个现有条目,或者你并不关心是否有一个同样的条目已经存在,使用put()
函数。
从数据库中删除数据
删除数据是非常类似的:
var request = db.transaction(["customers"], "readwrite")
.objectStore("customers")
.delete("444-44-4444");
request.onsuccess = function(event) {
// 删除数据成功!
};
从数据库中获取数据
现在数据库里已经有了一些信息,你可以通过几种方法对它进行提取。首先是简单的 get()
。你需要提供键来提取值,像这样:
var transaction = db.transaction(["customers"]);
var objectStore = transaction.objectStore("customers");
var request = objectStore.get("444-44-4444");
request.onerror = function(event) {
// 错误处理!
};
request.onsuccess = function(event) {
// 对 request.result 做些操作!
alert("Name for SSN 444-44-4444 is " + request.result.name);
};
对于一个“简单”的提取这里的代码有点多了。下面看我们怎么把它再缩短一点,假设你在数据库的级别上来进行的错误处理:
db.transaction("customers").objectStore("customers").get("444-44-4444").onsuccess = function(event) {
alert("Name for SSN 444-44-4444 is " + event.target.result.name);
};
这是如何工作的呢?由于只有一个对象存储空间,你可以避免传入一个在你的事务中需要的对象存储空间的列表,而只是作为一个字符串把名字传入即可。同样,你只是在从数据库读取数据,所以你不需要一个"readwrite"
事务。调用一个没有指定模式的 transaction()
将给你一个 "readonly"
事务。这里的另外一个微妙之处在于你实际上不需要保存请求对象到一个变量。因为 DOM 事件把这个请求作为它的 target,你可以使用 event 来得到result
属性。很简单,对吧?!
使用游标
使用 get()
要求你知道你想要检索哪一个键。如果你想要遍历对象存储空间中的所有值,那么你可以使用游标。看起来会像下面这样:
var objectStore = db.transaction("customers").objectStore("customers");
objectStore.openCursor().onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
alert("Name for SSN " + cursor.key + " is " + cursor.value.name);
cursor.continue();
}
else {
alert("No more entries!");
}
};
openCursor()
函数需要几个参数。首先,你可以使用一个 key range 对象来限制被检索的项目的范围。第二,你可以指定你希望进行迭代的方向。在上面的示例中,我们在以升序迭代所有的对象。游标成功的回调有点特别。游标对象本身是请求的result
(上面我们使用的是简写形式,所以是 event.target.result
)。然后实际的 key 和 value 可以根据游标对象的key
和 value
属性被找到。如果你想要保持继续前行,那么你必须调用游标上的 continue()
。当你已经到达数据的末尾时(或者没有匹配openCursor()
请求的条目)你仍然会得到一个成功回调,但是 result
属性是 undefined。
使用游标的一种常见模式是提取出在一个对象存储空间中的所有对象然后把它们添加到一个数组中,像这样:
var customers = [];
objectStore.openCursor().onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
customers.push(cursor.value);
cursor.continue();
}
else {
alert("Got all customers: " + customers);
}
};
警告: 下面的函数并不是 IndexedDB 标准的一部分!
Mozilla 也已经实现了 getAll()
来处理这种情况。它不是 IndexedDB 标准的一部分,所以它未来可能会消失。我们已经把它包含在这里是因为我们觉得它比较有用。下面的代码实现的是跟上面同样的事情:
objectStore.getAll().onsuccess = function(event) {
alert("Got all customers: " + event.target.result);
};
查找游标的 value
属性会引起相关的性能损耗,因为对象是被延迟创建的。当使用 getAll()
时,Gecko 必须立即创建所有的对象。如果你仅是对检索每个键感兴趣,举个例子,使用游标比使用getAll()
要高效的多。如果你试图获得一个对象存储空间内所有对象的一个数组,那么,使用 getAll()
。
使用索引
使用 SSN 作为键来存储客户数据是合理的,因为 SSN 唯一地标识了一个个体(对隐私来说这是否是一个好的想法是另外一个话题,不在本文的讨论范围内)。如果你想要通过姓名来查找一个客户,那么,你将需要在数据库中迭代所有的 SSN 直到你找到正确的那个。以这种方式来查找将会非常的慢,相反你可以使用索引。
var index = objectStore.index("name");
index.get("Donna").onsuccess = function(event) {
alert("Donna's SSN is " + event.target.result.ssn);
};
“name” 游标不是唯一的,因此 name
被设成 "Donna"
的记录可能不止一条。在这种情况下,你总是得到键值最小的那个。
如果你需要访问带有给定 name
的所有的记录你可以使用一个游标。你可以在索引上打开两个不同类型的游标。一个常规游标映射索引属性到对象存储空间中的对象。一个键索引映射索引属性到用来存储对象存储空间中的对象的键。不同之处被展示如下:
index.openCursor().onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
// cursor.key 是一个 name, 就像 "Bill", 然后 cursor.value 是整个对象。
alert("Name: " + cursor.key + ", SSN: " + cursor.value.ssn + ", email: " + cursor.value.email);
cursor.continue();
}
};
index.openKeyCursor().onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
// cursor.key is 一个 name, 就像 "Bill", 然后 cursor.value 是那个 SSN。
// 没有办法可以得到存储对象的其余部分。
alert("Name: " + cursor.key + ", "SSN: " + cursor.value);
cursor.continue();
}
};
指定游标的范围和方向
如果你想要限定你在游标中看到的值的范围,你可以使用一个 key range 对象然后把它作为第一个参数传给 openCursor()
或是openKeyCursor()
。你可以构造一个只允许一个单一 key 的 key range,或者一个具有下限或上限,或者一个既有上限也有下限。边界可以是闭合的(也就是说 key range 包含给定的值)或者是“开放的”(也就是说 key range 不包括给定的值)。这里是它如何工作的:
// 只匹配 "Donna"
var singleKeyRange = IDBKeyRange.only("Donna");
// 匹配所有在 "Bill" 前面的, 包括 "Bill"
var lowerBoundKeyRange = IDBKeyRange.lowerBound("Bill");
// 匹配所有在 “Bill” 前面的, 但是不需要包括 "Bill"
var lowerBoundOpenKeyRange = IDBKeyRange.lowerBound("Bill", true);
// Match anything up to, but not including, "Donna"
var upperBoundOpenKeyRange = IDBKeyRange.upperBound("Donna", true);
//Match anything between "Bill" and "Donna", but not including "Donna"
var boundKeyRange = IDBKeyRange.bound("Bill", "Donna", false, true);
index.openCursor(boundKeyRange).onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
// Do something with the matches.
cursor.continue();
}
};
有时候你可能想要以倒序而不是正序(所有游标的默认顺序)来遍历。切换方向是通过传递 prev
到 openCursor()
方法来实现的:
objectStore.openCursor(null, IDBCursor.prev).onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
// Do something with the entries.
cursor.continue();
}
};
因为 “name” 索引不是唯一的,那就有可能存在具有相同 name
的多条记录。要注意的是这种情况不可能发生在对象存储空间上,因为键必须永远是唯一的。如果你想要在游标在索引迭代过程中过滤出重复的,你可以传递nextunique
(或 prevunique
如果你正在向后寻找)作为方向参数。 当 nextunique
或是prevunique
被使用时,被返回的那个总是键最小的记录。
index.openKeyCursor(null, IDBCursor.nextunique).onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
// Do something with the entries.
cursor.continue();
}
};
当一个 web app 在另一个标签页中被打开时的版本变更
当你的 web app 在这样一种方式下改变你的数据库时碰到被要求进行版本变化,你需要考虑如果用户已经在一个标签页中打开了你的应用的旧版本的数据库,然后他又在另一个标签页中加载了你的应用的新版本,这种情况下会发生什么事情。当你带着比数据库实际版本更高的版本号调用open()
时,所有其他打开的数据库必须在你开始实际对数据库进行修改之前显式通知这个请求。这里是它如何工作的:
var openReq = mozIndexedDB.open("MyTestDatabase", 2);
openReq.onblocked = function(event) {
// 如果其他标签页已经加载了这个数据库,那么
// 在我们可以继续处理之前它需要被关闭。
alert("Please close all other tabs with this site open!");
};
openReq.onupgradeneeded = function(event) {
// 所有其它数据库都已经被关掉了。Set everything up.
db.createObjectStore(/* ... */);
useDatabase(db);
}
openReq.onsuccess = function(event) {
var db = event.target.result;
useDatabase(db);
return;
}
function useDatabase(db) {
// 确保添加一个如果另一个页面请求一个版本变化时来被通知的处理程序。
// 我们必须关闭这个数据库。这就允许其他页面对数据库进行升级。
// 如果你不这么做的话,除非用户关闭标签页否则升级就不会发生。
db.onversionchange = function(event) {
db.close();
alert("A new version of this page is ready. Please reload!");
};
// 其他针对数据库的处理
}
安全
IndexedDB 使用同源原则,这意味着它把存储空间绑定到了创建它的站点的源(典型情况下,就是站点的域或是子域),所以它不能被任何其他源访问。
要着重指出的一点是 IndexedDB 不适用于从另一个站点加载进框架的内容 (不管是 {{ HTMLElement("frame") }} 还是 {{ HTMLElement("iframe") }}。这是一项安全措施。为什么说这一点是很重要的,请查阅 {{ bug(595307) }}.