简介

IndexedDB是一种在浏览器端存储数据的方式。既然称之为DB,是因为它丰富了客户端的查询方式,并且因为是本地存储,可以有效的减少网络对页面数据的影响。

有了IndexedDB,浏览器可以存储更多的数据,从而丰富了浏览器端的应用类型。

IndexedDB简介

IndexedDB和传统的关系型数据不同的是,它是一个key-value型的数据库。

value可以是复杂的结构体对象,key可以是对象的某些属性值也可以是其他的对象(包括二进制对象)。你可以使用对象中的任何属性做为index,以加快查找。

IndexedDB是自带transaction的,所有的数据库操作都会绑定到特定的事务上,并且这些事务是自动提交了,IndexedDB并不支持手动提交事务。

IndexedDB API大部分都是异步的,在使用异步方法的时候,API不会立马返回要查询的数据,而是返回一个callback。

异步API的本质是向数据库发送一个操作请求,当操作完成的时候,会收到一个DOM event,通过该event,我们会知道操作是否成功,并且获得操作的结果。

IndexedDB是一种 NoSQL 数据库,和关系型数据库不同的是,IndexedDB是面向对象的,它存储的是Javascript对象。

IndexedDB还有一个很重要的特点是其同源策略,每个源都会关联到不同的数据库集合,不同源是不允许访问其他源的数据库,从而保证了IndexedDB的安全性。

IndexedDB的使用

这一节,我们将会以具体的例子来讲解如何使用IndexedDB。

IndexedDB的浏览器支持

不同的浏览器对于IndexedDB有不同的实现,正常来说,我们可以使用window.indexedDB来获取到浏览器的indexedDB对象。但是对于某些浏览器来说,还没有使用标准的window.indexedDB,而是用带前缀的实现。

所以我们在使用过程中通常需要进行判断和转换:

// In the following line, you should include the prefixes of implementations you want to test.
window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
// DON'T use "var indexedDB = ..." if you're not in a function.
// Moreover, you may need references to some window.IDB* objects:
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction || {READ_WRITE: "readwrite"}; // This line should only be needed if it is needed to support the object's constants for older browsers
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
// (Mozilla has never prefixed these objects, so we don't need window.mozIDB*)

上面我们从window获取了indexedDB,IDBTransaction和IDBKeyRange三个对象。

其中indexedDB表示的是数据库的连接。IDBTransaction表示的是transaction,而IDBKeyRange则是用从数据库的某个特定key range中取出数据。

但是,通常来说带前缀的实现一般都是不稳定的,所以我们通常不建议在正式环境中使用,所以如果不支持标准表达式的话,需要直接报错:

if (!window.indexedDB) {
console.log("Your browser doesn't support a stable version of IndexedDB. Such and such feature will not be available.");
}

创建IndexedDB

要使用IndexedDB,我们首先需要open it:

// Let us open our database
var request = window.indexedDB.open("MyTestDatabase", 3);

open方法返回一个IDBOpenDBRequest对象,同时这是一个异步操作,open操作并不会立马打开数据库或者开启事务,我们可以通过监听request的事件来进行相应的处理。

open方法传入两个参数,第一个参数是数据库的名字,第二个参数是数据库的版本号。

当你创建一个新的数据库或者升级一个现有的数据库版本的时候,将会触发一个onupgradeneeded事件,并在事件中传入IDBVersionChangeEvent,我们可以通过event.target.result来获取到IDBDatabase对象,然后通过这个对象来进行数据库的版本升级操作。如下所示:

// This event is only implemented in recent browsers
request.onupgradeneeded = function(event) {
// Save the IDBDatabase interface
var db = event.target.result; // Create an objectStore for this database
var objectStore = db.createObjectStore("name", { keyPath: "myKey" });
};

注意,这里的版本号是一个整数。如果你传入一个float,那么将会对该float进行取整操作。

有了request,我们可以通过监听onerror或者onsuccess事件来进行相应的处理。

var db;
var request = indexedDB.open("MyTestDatabase");
request.onerror = function(event) {
console.log("Why didn't you allow my web app to use IndexedDB?!");
};
request.onsuccess = function(event) {
db = event.target.result;
};

拿到db对象之后,我们可以设置全局的异常处理:

db.onerror = function(event) {
// Generic error handler for all errors targeted at this database's
// requests!
console.error("Database error: " + event.target.errorCode);
};

IndexedDB中的table叫做object stores,和关系型数据库中的table一样,object stores中的每一个对象都和一个key相关联,和key相关的有两个概念 key path 和 key generator.

如果存储的是javascript Object对象,那么可以指定该对象中的某一个属性作为key path,那么这个属性将会被作为key。

如果没有指定key path,那么存储的Object可以是任何对象,甚至是基础类型比如数字和String。

而key generator就是key的生成器。

假如我们想要存储这样的数据:

// This is what our customer data looks like.
const customerData = [
{ ssn: "444-44-4444", name: "Bill", age: 35, email: "bill@company.com" },
{ ssn: "555-55-5555", name: "Donna", age: 32, email: "donna@home.org" }
];

看一下对应的数据库操作是怎么样的:

const dbName = "the_name";

var request = indexedDB.open(dbName, 2);

request.onerror = function(event) {
// Handle errors.
};
request.onupgradeneeded = function(event) {
var db = event.target.result; // Create an objectStore to hold information about our customers. We're
// going to use "ssn" as our key path because it's guaranteed to be
// unique - or at least that's what I was told during the kickoff meeting.
var objectStore = db.createObjectStore("customers", { keyPath: "ssn" }); // Create an index to search customers by name. We may have duplicates
// so we can't use a unique index.
objectStore.createIndex("name", "name", { unique: false }); // Create an index to search customers by email. We want to ensure that
// no two customers have the same email, so use a unique index.
objectStore.createIndex("email", "email", { unique: true }); // Use transaction oncomplete to make sure the objectStore creation is
// finished before adding data into it.
objectStore.transaction.oncomplete = function(event) {
// Store values in the newly created objectStore.
var customerObjectStore = db.transaction("customers", "readwrite").objectStore("customers");
customerData.forEach(function(customer) {
customerObjectStore.add(customer);
});
};
};

我们需要在onupgradeneeded事件中处理所有的schema相关的操作。

首先使用db.createObjectStore创建了一个customers的ObjectStore,并且使用了对象的keypath作为key。

除了key之外,我们创建了两个index,以提高查询速度。

最后我们监听transaction.oncomplete事件,并在里面加入存储object的操作。

上面的代码中,我们使用了keyPath作为key。

下面是一个使用key Generator的例子:

 var objStore = db.createObjectStore("names", { autoIncrement : true });

indexdb中的CURD

indexedDB的所有操作都需要在事务中,我们看一个开启事务的操作:

var transaction = db.transaction(["customers"], "readwrite");

上面的例子中使用readwrite来操作customers ObjectStore。

transaction接收两个参数,第一个参数是一个数组,数组中是这个trans中将会处理的ObjectStores,第二个参数是处理的模式。

有了transaction之后,我们可以监听事务的complete和error操作,然后就可以进行add操作了:

// Do something when all the data is added to the database.
transaction.oncomplete = function(event) {
console.log("All done!");
}; transaction.onerror = function(event) {
// Don't forget to handle errors!
}; var objectStore = transaction.objectStore("customers");
customerData.forEach(function(customer) {
var request = objectStore.add(customer);
request.onsuccess = function(event) {
// event.target.result === customer.ssn;
};
});

上面的例子中,我们使用了add方法,add的前提是数据库中并不存在相同key的对象。除了add方法之外,我们还可以使用put方法,put方法主要用来进行更新操作。

再看一个删除的操作:

var request = db.transaction(["customers"], "readwrite")
.objectStore("customers")
.delete("444-44-4444");
request.onsuccess = function(event) {
// It's gone!
};

现在我们的数据库已经有了数据,我们看下怎么进行查询:

var transaction = db.transaction(["customers"]);
var objectStore = transaction.objectStore("customers");
var request = objectStore.get("444-44-4444");
request.onerror = function(event) {
// Handle errors!
};
request.onsuccess = function(event) {
// Do something with the request.result!
console.log("Name for SSN 444-44-4444 is " + request.result.name);

这里,我们直接使用了db.transaction,默认情况下是readonly模式的。

下面是一个更新的例子:

var objectStore = db.transaction(["customers"], "readwrite").objectStore("customers");
var request = objectStore.get("444-44-4444");
request.onerror = function(event) {
// Handle errors!
};
request.onsuccess = function(event) {
// Get the old value that we want to update
var data = event.target.result; // update the value(s) in the object that you want to change
data.age = 42; // Put this updated object back into the database.
var requestUpdate = objectStore.put(data);
requestUpdate.onerror = function(event) {
// Do something with the error
};
requestUpdate.onsuccess = function(event) {
// Success - the data is updated!
};
};

更新我们使用的是put方法。

使用游标cursor

indexedDB支持游标操作,我们可以使用cursor来遍历objectStore的数据:

var objectStore = db.transaction("customers").objectStore("customers");

objectStore.openCursor().onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
console.log("Name for SSN " + cursor.key + " is " + cursor.value.name);
cursor.continue();
}
else {
console.log("No more entries!");
}
};

openCursor可以接受多个参数,第一个参数可以接受key的查询范围,第二个参数用来指定遍历的方向。如果两个参数都为空的话,默认是所有的数据的以升序的顺序遍历。

如果想遍历下一个游标,则可以调用cursor.continue。

我们看一下两个参数的游标使用:

// Only match "Donna"
var singleKeyRange = IDBKeyRange.only("Donna"); // Match anything past "Bill", including "Bill"
var lowerBoundKeyRange = IDBKeyRange.lowerBound("Bill"); // Match anything past "Bill", but don't include "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); // To use one of the key ranges, pass it in as the first argument of openCursor()/openKeyCursor()
index.openCursor(boundKeyRange, "prev").onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
// Do something with the matches.
cursor.continue();
}
};

除了openCursor,我们还可以通过使用openKeyCursor来遍历KeyCursor:

// Using a normal cursor to grab whole customer record objects
index.openCursor().onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
// cursor.key is a name, like "Bill", and cursor.value is the whole object.
console.log("Name: " + cursor.key + ", SSN: " + cursor.value.ssn + ", email: " + cursor.value.email);
cursor.continue();
}
}; // Using a key cursor to grab customer record object keys
index.openKeyCursor().onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
// cursor.key is a name, like "Bill", and cursor.value is the SSN.
// No way to directly get the rest of the stored object.
console.log("Name: " + cursor.key + ", SSN: " + cursor.primaryKey);
cursor.continue();
}
};

除此之外,我们还可以直接通过index来进行查询:

var index = objectStore.index("name");

index.get("Donna").onsuccess = function(event) {
console.log("Donna's SSN is " + event.target.result.ssn);
};

要使用index的前提就是需要在request.onupgradeneeded中创建index。

本文作者:flydean程序那些事

本文链接:http://www.flydean.com/indexeddb-kickoff/

本文来源:flydean的博客

欢迎关注我的公众号:「程序那些事」最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

IndexedDB详解的更多相关文章

  1. Chrome开发者工具详解(5)-Application、Security、Audits面板

    Chrome开发者工具详解(5)-Application.Security.Audits面板 这篇文章是Chrome开发者工具详解这一系列的最后一篇,介绍DevTools最后的三个面板功能-Appli ...

  2. Chrome开发者工具详解(2)-Network面板

    Chrome开发者工具详解(2)-Network面板 注: 这一篇主要讲解面板Network,参考了Google的相关文档,主要用于公司内部技术分享. Chrome开发者工具面板 面板上包含了Elem ...

  3. Chrome开发者工具详解(1)-Elements、Console、Sources面板

    Chrome开发者工具详解(1)-Elements.Console.Sources面板 Chrome开发者工具面板 面板上包含了Elements面板.Console面板.Sources面板.Netwo ...

  4. 详解Google Chrome浏览器(操作篇)(下)

    开篇概述 由于最近忙于公司产品的架构与研发,已经三个多月没有写博客了,收到有些朋友的来信,问为什么不及时更新博客内容呢,他们说他们正期待着某些内容.对此,非常抱歉,那么我在此也给各位朋友一些承诺,从即 ...

  5. 前端数据存储方案集合(cookie localStorage等)以及详解 (二)

    前端数据存储方案集合(cookie localStorage等)以及详解 (二) 在之前的文章中已经介绍到了 前端存储方案中的 cookie . 但是 cookie 的存储上限是 4KB. 如果超过了 ...

  6. 详解Google Chrome浏览器(操作篇)(上)

    开篇概述 在上篇博客中详解Google Chrome浏览器(理论篇)一文中,主要讲解了Chrome 搜索引擎使用.Chrome安装和基本操作.Chrome 基本架构.多线程等原理性问题,这篇将重点讲解 ...

  7. 详解JavaScript的任务、微任务、队列以及代码执行顺序

    摘要: 理解JS的执行顺序. 作者:前端小智 原文:详解JavaScript的任务.微任务.队列以及代码执行顺序 思考下面 JavaScript 代码: console.log("scrip ...

  8. Linq之旅:Linq入门详解(Linq to Objects)

    示例代码下载:Linq之旅:Linq入门详解(Linq to Objects) 本博文详细介绍 .NET 3.5 中引入的重要功能:Language Integrated Query(LINQ,语言集 ...

  9. 架构设计:远程调用服务架构设计及zookeeper技术详解(下篇)

    一.下篇开头的废话 终于开写下篇了,这也是我写远程调用框架的第三篇文章,前两篇都被博客园作为[编辑推荐]的文章,很兴奋哦,嘿嘿~~~~,本人是个很臭美的人,一定得要截图为证: 今天是2014年的第一天 ...

随机推荐

  1. BlueCMS代码审计

    BlueCMS版本号为:bluecms_v1.6_sp1 本地搭建环境后将源代码丢进seay源代码审计系统,开启本地web服务页面访问,大部分白盒+小部分黑盒审计 搭建好环境后第一步先检查是否有重装漏 ...

  2. WinForm 加载大数据时不闪烁的ListView

    自己之前做小工具的时候,遇到这个问题,记录一下... namespace 相册处理 { //将ListView重载为此新类,解决加载过程中闪烁的问题 //在designer.cs中改写: //priv ...

  3. java视频格式转换

    项目中需要对各种视频格式转码为mp4格式,试了好多办法,最后使用ffmpeg 工具完美转码,ffmpeg能解析的格式:(asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等) 链接 ...

  4. dp斜率优化

    算法-dp斜率优化 前置知识: 凸包 斜率优化很玄学,凭空讲怎么也讲不好,所以放例题. [APIO2014]序列分割 [APIO2014]序列分割 给你一个长度为 \(n\) 的序列 \(a_1,a_ ...

  5. Kubernetes Python Client 初体验之Deployment

    Kubernetes官方推荐我们使用各种Controller来管理Pod的生命周期,今天写一个最常用的Deployment的操作例子. 首先是创建Deployment: with open(path. ...

  6. Java8的Lambda表达式,你会不?

    目录 理解Lambda 基础语法 函数式接口 常用的函数式接口 消费型接口 供给型接口 断言型接口 函数型接口 方法引用 数组引用 构造器引用 总结 参考阅读 理解Lambda Lambda表达式可以 ...

  7. proxySQL with SemiSync

    环境信息 hostname IP port role comm ms81 192.168.188.81 3399 master ms82 192.168.188.82 3399 slave ms83 ...

  8. f12 Network的解析

    Chrome开发者工具面板 面板上包含了Elements面板.Console面板.Sources面板.Network面板.Timeline面板.Profiles面板.Application面板.Sec ...

  9. 面试 03-DOM事件的总结

    03-DOM事件的总结 #前言 要学习事件的基础内容,请看先本人之前的基础文章: <04-JavaScript基础/27-事件对象Event> <04-JavaScript基础/28 ...

  10. eclipse 创建自己的Maven项目(超详细)

    本文章 主要是 讲解 是讲解 eclipse创建项目 --SpiritMark_liu 先配置 Maven 的 settings 地址 (Window -> Perferences–>Ma ...