Web Storage

在 HTML4 中可以使用 cookie 在客户端保存诸如用户名等简单的用户信息,但是,通过长期的使用,人们发现,用 cookie 储存永久数据存在以下几个问题:

  • 大小:cookie 的大小被限制在4KB;
  • 带宽:cookie 是随 HTTP 事务一起被发送的,因此会浪费一部分发送 cookie 时使用的带宽;
  • 复杂性:要正确地操纵 cookie 是很困难的。

针对这些问题,html5 提供了一种在客户端本地保存数据的功能:Web Storage。web Storage 分为两种:

  • sessionStorage:将数据保存到 session 对象中。session 对象可以用来保存在这段时间(从进入网站到浏览器关闭所经过的这段时间)内所要求保存的任何数据;
  • localStorage:将数据保存在客户端本地的硬件设备。即使浏览器被关闭了,该数据仍然存在。

这两者的区别在于,sessionStorage 为临时保存,而 localStorage 为永久保存。

webStorage的属性和方法

  • length:所有保存在 web Storage 中的数据的条数;
  • getItem(key):获取指定属性值的数据;
  • setItem(key, value):设置指定属性值的数据;
  • removeItem(key):移除指定属性值的数据;
  • key(index):返回指定索引号的数据的 key 值;
  • clear():所有保存在 web Storage 中的数据会被全部清除;

示例如下:

  1. window.localStorage.setItem('name', '张三');
  2. window.localStorage.setItem('age', '23');
  3. console.log(window.localStorage.getItem('name')); // '张三'
  4. console.log(window.localStorage.length); // 2
  5. console.log(window.localStorage.key(0)); // name
  6. // 对于一个多属性的对象,可以使用JSON.stringif()和JSON.parse()来进行对象和JSON对象的相互转换
  7. var person = {name: '张三', age: 23}
  8. window.localStorage.setItem('person', JSON.stringify(person));
  9. var personJSON = window.localStorage.getItem('person');
  10. var newPerson = JSON.parse(personJSON); // {name: "张三", age: 23}

利用storage事件实时监视web Storage中的数据

在 HTML5 中,可以通过对 window 对象的 storage 事件进行监听并指定其事件处理函数的方法来定义当在其他页面中修改 sessionStorage 或 localStorage 中的值时所要执行的处理。

在事件处理函数中,触发事件的事件对象(event 参数值)具有如下几个属性:

  • key:属性值为在 session 或 localStorage 中被修改的数据键值;
  • oldValue:属性值为在 sessionStorage 或 localStorage 中被修改前的值;
  • newValue:属性值为在 sessionStorage 或 localStorage 中被修改后的值;
  • url:属性值为修改 sessionStorage 或 localStorage 中值的页面URL地址;
  • storageArea:属性值为被变动的 sessionStorage 对象或 localStorage 对象。

接下来通过一个示例展示。这里使用了两个页面,第一个页面用于修改 localStorage 数据,第二个页面设置 localStorage 的监听事件,当页面一修改 localStorage 中的数据时,可以在页面二中实时响应。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>修改WebStorage中的数据</title>
</head>
<body>
  请输入值:<input type="text" id="text1" />
  <button onclick="setLocalStorage()">设置</button>
</body>
</html>
<script>
function setLocalStorage() {
  localStorage.test = document.getElementById('text1').value;
}
</script>
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>利用storage事件实时监视webStorage中的数据</title>
</head>
<body>
  <div id="output"></div>
</body>
</html>
<script>
    window.addEventListener('storage', function(event) {
    console.log(event);
    if(event.key == "test") {
      var output = document.getElementById('output');
      output.innerHTML = "原有值:" + event.oldValue;
      output.innerHTML += "<br/>新值: " + event.newValue;
      output.innerHTML += "<br/>变动页面地址:" + event.url;
      output.innerHTML += "<br/>storage对象: "+ JSON.stringify(event.storageArea);
    }
  }, false);
</script>

最终效果如下:
image.png

页面二输出:
image.png

indexedDB数据库

indexedDB的基本概念

在 HTML5 中,新增一种被称为 indexedDB 的数据库,该数据库是一种存储在客户端本地的 NoSQL 数据库,目前Chrome 11以上、Firefox 4以上、Opera 18以上、Safari 8以及IE 10版本的浏览器对其提供支持。

连接数据库

在各浏览器中使用 indexedDB 数据库时,首先需要对 indexedDB 数据库、该数据库所使用的事务、IDBKeyRange 对象与游标对象进行预定义,为了使脚本代码可以运行在各浏览器中,我们针对各浏览器进行统一定义:

// 在各浏览器中都能运行的统一定义
window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
window.IDBCursor = window.IDBCursor || window.webkitIDBCursor || window.msIDBCursor;

接着连接某个 indexedDB 数据库,代码如下。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>修改WebStorage中的数据</title>
</head>
<body>
  <input type="button" value="连接数据库" onclick="connectDatabase();">
</body>
</html>
<script>
// 在各浏览器中都能运行的统一定义
window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
window.IDBCursor = window.IDBCursor || window.webkitIDBCursor || window.msIDBCursor;

function connectDatabase() {
  var dbName = 'indexedDBTest'; // 数据库名
  var dbVersion = 20120603; //版本号
  var idb;

  /* 连接数据库,dbConnect对象为一个IDBOpenDBRequest对象,代表数据库连接的请求对象*/
  var dbConnect = indexedDB.open(dbName, dbVersion);
  // 连接成功
  dbConnect.onsuccess = function(e) {
    // e.target.result为一个IDBDatabase对象,代表连接成功的数据库对象
    idb = e.target.result;
    console.log('数据库连接成功');
  }
  dbConnect.onerror = function() {
    console.log('数据库连接失败');
  }
}
</script>

indexedDB.open 接收两个参数,第一个参数是数据库名,第二个参数是数据库版本号,返回一个 IDBOpenRequest 对象,代表一个请求连接数据库的请求对象。接着通过监听数据库连接的请求对象的 onsuccess 事件和 onerror 事件来定义数据库连接成功或失败的事件处理函数。在成功的处理函数中,e.targe.result 代表连接成功的数据库对象。

关闭数据库

在 indexedDB API 中,可以通过 indexedDB 数据库对象的 close() 方法关闭数据库连接。当连接关闭之后,不能继续执行数据库进行的操作,否则将抛出 InvalidStateError 异常,代表数据库连接已被关闭。

idb.close();

数据库的版本更新

在使用 indexedDB 数据库的时候,所有对于数据的操作都在一个事务内部执行。事务分为三种:只读事务、读写事务与版本更新事务

对于创建对象仓库与索引的操作,我们只能在版本更新事务内部进行,因为在 indexedDB API 中不允许数据库中的数据仓库(相当于关系型数据库中的数据表)在同一个版本中发生变化,所以当我们创建或删除数据仓库时,必须使用新的版本号来更新数据库的版本,以避免重复修改数据库结构。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>修改WebStorage中的数据</title>
</head>
<body>
  <input type="button" value="数据库版本更新" onclick="versionUpdate();">
</body>
</html>
<script>
// 在各浏览器中都能运行的统一定义
window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
window.IDBCursor = window.IDBCursor || window.webkitIDBCursor || window.msIDBCursor;

function versionUpdate() {
  var dbName = 'indexedDBTest';                 // 数据库名
  var dbVersion = 20150304;                         // 数据库版本
  var idb;

  /* 连接数据库,dbConnect对象为一个IDBOpenDBRequest对象 */
  var dbConnect = indexedDB.open(dbName, dbVersion);
  dbConnect.onsuccess = function(e) {
    idb = e.target.result;
    console.log('数据库连接成功');
  }
  dbConnect.onerror = function() {
    console.log('数据库连接失败');
  }

  /* 数据库版本更新 */
  dbConnect.onupgradeneeded = function(e) {
    // e.target.result为一个IDBDatabase对象,代表连接成功的数据库对象
    idb = e.target.result;

    // e.target.transaction属性值为一个IDBTransaction事务对象,这里代表版本更新事务
    var tx = e.target.transaction;
    var oldVersion = e.oldVersion;  // 更新前的版本号
    var newVersion = e.newVersion;  // 更新后的版本号
    console.log('数据库版本更新成功, 旧版本号:'+oldVersion+', 新版本号:'+newVersion);

    /* 此处执行创建对象仓库等处理 */
  }
}
</script>

这里通过 onupgradeneeded 来监听版本更新事件,并且指定在事件触发时所执行的处理。

:::info 当连接数据库时,发现指定的版本号大于数据库当前版本号时触发 onupgradeneeded 事件,当该事件被触发时,一个数据库的版本更新事务已经被开启,同时数据库的版本号已经被自动更新完毕。 :::

创建对象仓库(类似于数据表)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>修改WebStorage中的数据</title>
</head>
<body>
  <input type="button" value="创建对象仓库" onclick="createObjectStore();">
</body>
</html>
<script>
// 在各浏览器中都能运行的统一定义
window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
window.IDBCursor = window.IDBCursor || window.webkitIDBCursor || window.msIDBCursor;

function createObjectStore() {
  var dbName = 'indexedDBTest';                 // 数据库名
  var dbVersion = 20150305;                         // 数据库版本
  var idb;

  /* 连接数据库,dbConnect对象为一个IDBOpenDBRequest对象 */
  var dbConnect = indexedDB.open(dbName, dbVersion);
  dbConnect.onsuccess = function(e) {
    idb = e.target.result;
    console.log('数据库连接成功');
  }
  dbConnect.onerror = function() {
    console.log('数据库连接失败');
  }

  /* 数据库版本更新 */
  dbConnect.onupgradeneeded = function(e) {
    // e.target.result为一个IDBDatabase对象,代表连接成功的数据库对象
    idb = e.target.result;

    // e.target.transaction属性值为一个IDBTransaction事务对象,这里代表版本更新事务
    var tx = e.target.transaction;

    var name = "Users";
    var optionalParameters = {
        keyPath: 'userId',
      autoIncrement: false
    };

    var store = idb.createObjectStore(name, optionalParameters);
    console.log('对象仓库创建成功');

    /* 索引创建部分 */
  }
}
</script>

image.png
在这段代码中,监听数据库连接请求对象的 onupgradeneeded 事件,并且在该事件触发时调用数据库对象的 createObjectStore() 创建对象仓库。

createObjectStore 接收两个参数,第一个参数是对象仓库名,第二个参数用于指定对象仓库中的每一条记录使用哪个属性值来作为该记录的主键值(数据仓库中该记录的唯一标识符)。

:::info 知识点:

  • 在一个对象仓库中,只能有一个主键。相当于关系型数据库中数据表的 id 字段为数据表的主键;
  • 对象仓库中的每一条记录是具有一个或多个属性值的对象,而 keyPath 属性值用于指定每条记录使用哪个属性值作为主键值;如上代码中将 userId 作为每条记录的主键值,相当于关系型数据库中将每条记录的 userId 字段值指定为该记录的主键值。这种情况下,因为主键存在于每条记录内部,所以称为内联主键,如果这里不指定 optionalParameters 对象中的 keyPath 属性值或将其指定为 null,每条记录的主键将通过其他的途径被另行指定,这时因为数据记录的主键存在于每条记录之外,被称为外部主键
  • autoIncrement 属性值 为 true,相当于在关系型数据库中将主键指定为自增主键,如果添加数据记录时不指定主键值,则在数据仓库内部将自动指定该主键值为既存的最大主键值+1,当然,也可以在添加数据记录时显式地指定主键值。如果将该属性值指定为 false,则必须在添加数据记录时显式地指定主键值。 :::

创建索引

indexedDB 数据库中的索引类似于关系型数据库中的索引,需要通过数据记录对象的某个属性值来创建。创建索引后,可以提高在对数据仓库中的所有数据记录进行检索时的性能。

在 indexedDB 中,只能通过被设为索引的属性值来进行检索

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>修改WebStorage中的数据</title>
</head>
<body>
  <input type="button" value="创建索引" onclick="createIndex();">
</body>
</html>
<script>
// 在各浏览器中都能运行的统一定义
window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
window.IDBCursor = window.IDBCursor || window.webkitIDBCursor || window.msIDBCursor;

function createIndex() {
  var dbName = 'indexedDBTest';
  var dbVersion = 20150306;
  var idb;

  var dbConnect = indexedDB.open(dbName, dbVersion);
  dbConnect.onsuccess = function(e) {
    idb = e.target.result;
    console.log('数据库连接成功');
  }
  dbConnect.onerror = function() {
    console.log('数据库连接失败');
  }

  dbConnect.onupgradeneeded = function(e) {
    idb = e.target.result;

    var tx = e.target.transaction;
    var name = 'newUsers';
    var optionalParameters = {
      keyPath: 'userId',
      autoIncrement: false
    };

    var store = idb.createObjectStore(name, optionalParameters);
    console.log('对象仓库创建成功');

    var name = 'userNameIndex';
    var keyPath = 'userName';
    var indexParameters = {
      unique: false,
      multiEntry: false
    };

    // 调用对象仓库的createIndex方法创建索引
    var idx = store.createIndex(name, keyPath, indexParameters);
    console.log('索引创建成功');
  }  
}
</script>

image.png
在这里,createIndex 接收三个参数,第一个参数值为一个字符串,代表索引名;第二个参数值代表使用数据仓库中数据记录对象的哪个属性来创建索引;第三个参数是一个 javascript 对象,unique 为 true 时代表同一个对象仓库中两条数据记录的索引属性值(如上是 userName)不能相同,否则会添加失败;multiEntry 为 true 时代表当数据记录的索引值为一个数组的时候,可以将数组中的每一个元素添加到索引中,为 false 时,代表只能将该数组整体添加到索引中。

在 indexedDB 2.0 中,允许在版本更新事务中修改对象仓库的名称及索引的名称。如下:

dbConnect.onupgradeneeded = function (e) {
    let tx = e.target.transaction;
  let store = tx.objectStore("Users");
  store.name = "newUsers";
  let index = store.index("userNameIndex");
  index.name = "unIndex";
}

索引的multiEntry属性值

前面说到,索引具有一个 multiEntry 属性,当该属性值为 true 时,如果数据记录的索引属性值为一个数组,可以将该数组中的每一个元素添加到索引中;如果该属性值为 false,则必须将该数组作为一个整体添加到索引中。默认值为 false。

为了更好的理解这个概念,下面以记录为例来说明。假设一条文章记录对象如下所示。

{
    id: 12345,
  title: '文章标题',
  body: '文章正文',
  tags: ['HTML', 'JavaScript', 'PHP']
}

该记录的 tags 属性用于说明该文章涉及哪几个方面的内容,其属性值为一个数组。在关系型数据库中,如果一条记录具有多个字段值,往往需要另外一张数据表来保存该字段,但是在 indexedDB 数据库中,可以将 tags 属性值保存为一个数组。

:::info 如果将 tags 属性创建为一个索引,并且将该索引的 multiEntry 属性值设定为 true,那么无论使用 HTML、JavaScript 还是 PHP 对 tags 属性进行检索,都可以检索出该条记录,如果将该索引的 multiEntry 属性值设定为 false,则必须使用“[‘HTML’,’JavaScript’,’PHP’]”这种数组的形式对 tags 属性进行检索,才可以检索出该条记录。 :::

到目前为止,我们已经介绍了如何在数据库中创建对象仓库与索引,接下来将开始对数据进行增、删、查、改操作。

使用事务

前面说过,在 indexedDB API 中,所有针对数据的操作都只能在一个事务中被执行。同时也提到过,事务分为三种:只读事务、读写事务与版本更新事务。而如何开启一个版本更新事务已经在前面说过。

下面介绍如何开启只读事务读写事务。在数据库连接成功后,可以使用如下方法开启只读事务与读写事务。

var storeNames = ['Users'];
var mode = "readonly";                    // 只读事务
// var mode = "readwrite";                    // 读写事务

// idb为某个已连接数据库
var tx = idb.transaction(storeNames, mode);        // 开启事务

// 针对单个对象仓库名
var tx = idb.transaction('Users', 'readonly');

使用已建立连接的数据库对象的 transaction 方法来开启事务,它接收两个参数,第一个参数是事务运行的操作是对哪些对象仓库进行,可以是仓库名数组或仓库名字符串(针对单个时)。

如果不想限定事务只针对哪些对象仓库进行,那么可以使用 objectStoreName 属性值来作为第一个参数,objectStoreName 属性值为由该数据库中所有对象仓库名构成的数组,将其作为第一个参数时,可以针对数据库中任何一个对象仓库进行数据的存取操作。

第二个参数用于定义事务的读写模式。当值为 “readonly”时为只读事务,值为“readwrite”时为读写事务,默认是只读事务。

transaction 方法返回一个 IDBTransaction 对象,代表被开启的事务。

:::info 当将数据库的 objectStoreNames 属性值作为 transaction 方法的第一个参数值,将“readwrite”常量值作为 transaction 方法的第二个参数值运行 transaction 方法之后,虽然可以无需再关注事务是针对哪些对象仓库进行,以及事务是读写事务还是只读事务,但是考虑到这样会对性能产生很大的影响,所以最好还是正确指定事务的作用范围。 :::

在 indexedDB API 中,可以同时运行多个作用范围不重叠的读写事务,如果数据库中存在 storeA 与 storeB 两个对象仓库,事务 A 的作用范围为 storeA,事务 B 的作用范围为 storeB,那么可以同时运行事务 A 与事务 B。如果发生重叠时,则需要根据运行顺序来判定运行顺序。

在 indexedDB API 中,用于开启事务的 transaction 方法必须被书写到某一个函数中,而且该事务将在函数结束时被自动提交(commit),所以不需要显式调用事务的 commit 方法来提交事务,但是可以在需要的时候显式调用事务的 abort 方法来中止事务。

var tx = idb.transaction(['Users'], 'readwrite');                // 开启事务

/*
 * 事务中的处理内容
 */

tx.abort();                    // 中止事务

可以通过监听事务对象的 oncomplete 事件(事务结束时触发)及 onabort 事件(事务被中止时触发)并定义事件处理函数来定义事务结束或中止时所要执行的处理。

var tx = idb.transactioni(['Users'], 'readwrite');
tx.oncomplete = function(e) {
  // 事务结束时执行的处理
}

tx.onabort = function(e) {
    // 事务中止时执行的处理
}

/*
 * 事务中的处理内容
 */

保存数据

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>修改WebStorage中的数据</title>
</head>
<body>
  <input type="button" value="保存数据" onclick="saveData();">
</body>
</html>
<script>
// 在各浏览器中都能运行的统一定义
window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
window.IDBCursor = window.IDBCursor || window.webkitIDBCursor || window.msIDBCursor;

function saveData() {
  var dbName = 'indexedDBTest';
  var dbVersion = 20150306;
  var idb;

  var dbConnect = indexedDB.open(dbName, dbVersion);    // 连接数据库
  dbConnect.onsuccess = function(e) {
    idb = e.target.result;

    // 通过数据库对象的transaction方法开启事务
    var tx = idb.transaction(['Users'], 'readwrite');

    // 通过被开启的事务对象tx的objectStore方法获取作用事务对象作用的对象仓库Users
    // 它返回一个IDBObjectStore对象,代表获取成功的对象仓库store
    var store = tx.objectStore('Users');
    var value = {
      userId: 1,
      userName:'张三',
      address: '住址1'
    };

    // 使用对象仓库store的put方法向数据库发出保存数据的请求,put接收需要保存的数据对象
    // put返回一个IDBRequest对象,代表一个向数据库发出的请求
    // 请求发出后将立即被异步执行,可以通过监听请求对象的onsuccess和onerror事件并指定
    // 对应的处理函数来对不同的情况作出处理
    var req = store.put(value);

    req.onsuccess = function(e) {
      console.log('数据保存成功');
    }
    req.onerror = function(e) {
      console.log('数据保存失败');
    }
  }

  dbConnect.onerror = function() {
    console.log('数据库连接失败');
  }
}
</script>

image.png

根据对象仓库的主键是内联主键还是外部主键,主键是否被指定为自增主键,对象仓库的 put 方法的第一个参数值的指定方法也各不相同。具体如下所示:

// 主键为自增、内联主键时不需要指定主键值
store.put({ userName: '张三', address: '住址1' });

// 主键为内联、非自增主键时需要指定主键值
store.put({ userId:1, userName: '张三', address: '住址1' });

// 主键为外部主键时需要另行指定主键值(此处假设主键值为1)
store.put({ userName: '张三', address: '住址1' }, 1);

在 indexedDB API 中,对象仓库还有一个 add 方法,区别在于当使用 put 方法保存数据时,如果指定的主键值在对象仓库中已存在,那么该主键值所在数据被更新为使用 put 方法所保存的数据,而在使用 add 方法保存数据时,如果指定的主键值在对象仓库中已存在,那么保存失败。

// 使用put方法保存数据将提示保存成功
(function() {
    var tx = idb.transaction(['Users'], 'readwrite');
    tx.oncomplete = function() {alert('保存数据成功');};
    tx.onabort = function() {alert('保存数据失败');};

  var store = tx.objectStore('Users');
  store.put({ userId: 200, userName: '李四' });
  store.put({ userId: 200, userName: '王五' });
})();

// 使用add方法保存数据将提示保存失败
(function() {
    var tx = idb.transaction(['Users'], 'readwrite');
    tx.oncomplete = function() {alert('保存数据成功');};
    tx.onabort = function() {alert('保存数据失败');};

  var store = tx.objectStore('Users');
  store.add({ userId: 100, userName: '李四' });
  store.add({ userId: 100, userName: '王五' });
})();

在indexedDB数据库中保存Blob对象

到目前为止,Chrome 37以上、Firefox 17以上、IE 10以上以及Opera 24以上版本浏览器支持在 indexedDB 数据库中保存 Blob 对象。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>修改WebStorage中的数据</title>
</head>
<body>
  <p>
    <input type="file" id="file" />
    <input type="button" value="在indexedDB数据库中保存文件" onclick="saveFile();">
  </p>
</body>
</html>
<script>
// 在各浏览器中都能运行的统一定义
window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
window.IDBCursor = window.IDBCursor || window.webkitIDBCursor || window.msIDBCursor;

function saveFile() {
  const dbName = "blobTest";
  const dbVersion = 20180407;
  let dbConnect = indexedDB.open(dbName, dbVersion);
  let idb;

  dbConnect.onsuccess = function(e) {
    idb = e.target.result;

    let file = document.getElementById('file').files[0];
    let tx = idb.transaction(['files'], 'readwrite');
    let store = tx.objectStore('files');
    let req = store.put(file, 'blob');
    req.onsuccess = function() {
      console.log('文件保存成功');
    }
    req.onerror = function() {
      console.log('文件保存失败');
    }
  }

  dbConnect.onerror = function() {
    console.log('数据库连接失败');
  }

  dbConnect.onupgradeneeded = function(e) {
    idb = e.target.result;
    idb.createObjectStore('files');
  }
}
</script>

image.png

示例页面中显示一个文件选取控件及一个“在indexedDB数据库中保存文件”按钮,用户通过控件选取文件后用鼠标单击“在indexedDB数据库中保存文件”按钮,脚本程序将把用户选取文件保存到 indexedDB 数据库中。

自2.0版开始,二进制数据类型可以被指定为索引属性值,也就是说,对象中被存储的 Blob 对象、Arraybuffer、类型数组对象以及 DataView 对象都可被索引。开发者现在可以直接拥有二进制索引,不再需要将它们转换为字符串或数组对象。

const dbName = 'blobTest1';
const dbVersion = 20180408;

let dbConnect = indexedDB.open(dbName, dbVersion);
let idb;

dbConnect.onsuccess = function(e) {
    idb = e.target.result;
}
dbConnect.onerror = function(e) {
    console.log("数据库连接失败");
}
dbConnect.onupgradeneeded = function(e) {
    idb = e.target.result;
  let store = idb.createObjectStore('files');
    let name = 'fileIndex';
  let keyPath = "file";
  let optionalParameters = {
      unique: false,
    multiEntry: false
  };

  let idx = store.createIndex(name, keyPath, optionalParameters);
  console.log('索引创建成功');
}

获取数据

下面介绍如何从 indexedDB 数据库的对象仓库中获取数据。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>修改WebStorage中的数据</title>
</head>
<body>
  <input type="button" value="获取数据" onclick="getData();">
</body>
</html>
<script>
// 在各浏览器中都能运行的统一定义
window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
window.IDBCursor = window.IDBCursor || window.webkitIDBCursor || window.msIDBCursor;

function getData() {
  var dbName = 'indexedDBTest';
  var dbVersion = 20150306;
  var idb;

  var dbConnect = indexedDB.open(dbName, dbVersion);
  dbConnect.onsuccess = function(e) {
    idb = e.target.result;
    var tx = idb.transaction(['Users'], 'readonly');
    var store = tx.objectStore('Users');

    var req = store.get(1);

    // 如果对象仓库中存在由数据记录的其他属性组成的索引,则可以根据该索引的属性来获取数据,例如
    // 对象仓库的index方法来获取某个索引,参数值为所需获取索引的名称,接着利用索引的get方法获取数据
    // var idx = store.index('userNameIndex');    
    // var req = idx.get('张三');

    req.onsuccess = function() {
      if(this.result === undefined) {
        console.log('没有符合条件的数据');
      } else {
        console.log('获取数据成功,用户名为'+this.result.userName);
      }
    }

    req.onerror = function() {
      console.log('获取数据失败');
    }
  }

  dbConnect.onerror = function() {
    console.log('数据库连接失败');
  }
}
</script>

image.png

在这个例子中,首先连接数据库,接着开启一个只读事务,同时返回操作的对象仓库,通过对象仓库的 get 方法从对象仓库中获取一条数据。get 接收一个参数,指定所需获取数据的主键值。

与 put、add 类似,get 返回的是一个 IDBRequest 获取数据的请求对象。请求发出后立即异步执行,设置监听函数 onsuccess 和 onerror 处理函数。在执行成功后,如果没有获取到符合条件的数据那么 result 值为 undefined,如果有则输出结果。

根据主键值检索数据

通过对象仓库或索引的 get 方法,我们只能获取到一条数据。在需要通过某个检索条件来检索一批数据时,我们需要使用 indexedDB API 中的游标。

在 indexedDB API 中,可以根据数据记录的主键值检索数据。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body onload="window_onload()">
  <input type="button" value="连接数据库" onclick="connectDataBase();" />
  <input type="button" id="btnSaveData" value="保存数据" onclick="saveData();">
  <input type="button" id="btnSearchData" value="检索数据" onclick="searchData();">
</body>
</html>
<script>
// 在各浏览器中都能运行的统一定义
window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
window.IDBCursor = window.IDBCursor || window.webkitIDBCursor || window.msIDBCursor;

var dbName = 'indexedDBTest';
var dbVersion = 20150306;
var idb;

function window_onload(){
  document.getElementById('btnSaveData').disabled = true;
  document.getElementById('btnSearchData').disabled = true;
}

function connectDataBase() {
  var dbConnect = indexedDB.open(dbName, dbVersion);
  dbConnect.onsuccess = function(e) {
    idb = e.target.result;
    console.log('数据库连接成功');
    document.getElementById('btnSaveData').disabled = false;
  }
  dbConnect.onerror = function() {
    console.log('数据库连接失败');
  }
}

function saveData() {
  var tx = idb.transaction(['Users'], "readwrite");
  tx.oncomplete = function() {
    console.log('保存数据成功');
    document.getElementById('btnSearchData').disabled = false;
  }
  tx.onabort = function() {console.log('保存数据失败');}
  var store = tx.objectStore('Users');

  var value1 = {
    userId: 1,
    userName: '张三',
    address: '住址1'
  };
  store.put(value1);

  var value2 = {
    userId: 2,
    userName: '李四',
    address: '住址2'
  };
  store.put(value2);

  var value3 = {
    userId: 3,
    userName: '王五',
    address: '住址3'
  };
  store.put(value3);

  var value4 = {
    userId: 4,
    userName: '赵六',
    address: '住址4'
  };
  store.put(value4);
}

function searchData() {
  // 开启只读事务
  var tx = idb.transaction(['Users'], 'readonly');
  // 获取对象仓库Users
  var store = tx.objectStore('Users');

  // 通过游标来检索主键值为1到4的数据,并将检索到数据的userName输出
  var range = IDBKeyRange.bound(1, 4);
  var direction = "next";
  // 通过openCursor创建并打开一个游标
  var req = store.openCursor(range, direction);
  req.onsuccess = function() {
    var cursor = this.result;

    // 如果检索到数据
    if (cursor) {
      console.log('检索到一条数据,用户名为:'+cursor.value.userName);  // 输出当前游标数据的userName值
      cursor.continue();    // continue() 读取下一条数据记录
    } else {
      console.log('检索结束');
    }
  }
  req.onerror = function() {
    console.log('检索数据失败');
  }
}
</script>

通过对象仓库的 openCursor 方法创建并打开一个游标,该方法接收两个参数,第一个参数为一个 IDBKeyRange 对象,该对象创建方法有如下几种:

  • bound(lower,upper,lowerOpen, upperOpen) :返回一个由一批数据的主键值组成的 IDBKeyRange 集合对象。当游标打开时,该集合中的所有主键值所指向的数据均被读取到游标中。接收四个参数,第一个、第二个参数分别代表的是集合中的最小主键值、最大主键值,第三、四个参数是布尔值,默认都为 false,如果第三个参数为 false 时代表包含第一个参数的最小主键值,否则就排除第一个参数的最小主键值,同理,第四个参数类似,表示是否包含(false)/排除(true)最大主键值。
  • only(value) :返回一个由一条数据的主键值组成的 IDBKeyRange 集合对象。游标打开时,这条数据将被读取到游标中。接收一个参数,代表该数据的主键值。
  • lowerBound(lower, lowerOpen) :返回一个由一批数据的主键值组成的 IDBKeyRange 集合对象,这批数据中所有数据的主键值均大于等于指定的主键值 lower。lowerOpen 类似于 bound 中的 lowerOpen,这里不再赘述。
  • upperBound(upper, upperOpen) :返回一个由一批数据的主键值组成的 IDBKeyRange 集合对象,这批数据中所有数据的主键值均小于等于指定的主键值 upper。upperOpen 类似于 bound 中的 upperOpen ,这里不再赘述。

openCursor 的第二个参数 direction 用于指定游标的读取方法。可指定该值为如下:

  • next:游标中数据按主键值升序排列,主键值相等的数据均被读取到游标中;
  • nextunique:游标中数据按主键值升序排列,主键值相等时只读取第一条数据;
  • prev:游标中数据按主键值降序排列,主键值相等的数据均被读取到游标中;
  • prevunique:游标中数据按主键值降序排列,主键值相等时只读取第一条数据;

openCursor 返回一个 IDBRequest 对象,代表一个向数据库发出的检索数据的请求。在检索成功后,如果不存在符合检索条件的数据,那么请求对象的 result 属性值为 null 或 undefined,检索终止;可通过判断该属性值是否为 null 或 undefined 来判断检索是否终止并指定检索终止时的处理。

如果存在符合检索结果的数据,那么请求对象的 result 属性值为一个 IDBCursorWithValue 对象,该对象的 key 属性值中保存了游标中当前指向的数据记录的主键值,value 属性值为一个对象,代表当前的数据记录。示例中通过 cursor.value.userName 来获取记录的 userName。

同时,当存在符合检索条件的数据时,可通过 IDBCursorWithValue 对象的 update 方法更新该条数据。

cursor.update({
    userId: cursor.key,
  userName: 'test',
  address: 'test'
});

可通过 IDBCursorWithValue 对象的 delete 方法删除该条数据。

cursor.delete();

可通过 IDBCursorWithValue 对象的 continue 方法读取游标中的下一条数据记录。

cursor.continue();

执行示例代码,最终效果如下图所示。
image.png

根据搜索范围获取数据的主键值

在 indexedDB 2.0 中,处于性能考虑,新增对象仓库的 getKey 方法,使其可以获取对象仓库中第一条处于搜索范围中的数据的主键值。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body onload="window_onload()">
  <input type="button" value="连接数据库" onclick="connectDataBase();" />
  <input type="button" id="btnSaveData" value="保存数据" onclick="saveData();">
  <input type="button" id="btnSearchData" value="检索数据" onclick="searchData();">
</body>
</html>
<script>
// 在各浏览器中都能运行的统一定义
window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
window.IDBCursor = window.IDBCursor || window.webkitIDBCursor || window.msIDBCursor;

var dbName = 'indexedDBTest';
var dbVersion = 20150306;
var idb;

function window_onload(){
  document.getElementById('btnSaveData').disabled = true;
  document.getElementById('btnSearchData').disabled = true;
}

function connectDataBase() {
  var dbConnect = indexedDB.open(dbName, dbVersion);
  dbConnect.onsuccess = function(e) {
    idb = e.target.result;                // 引用IDBDatabase对象
    console.log('数据库连接成功');
    document.getElementById('btnSaveData').disabled = false;
  }
  dbConnect.onerror = function() {
    console.log('数据库连接失败');
  }

  dbConnect.onupgradeneeded = function(e) {
    // e.target.result为一个IDBDatabase对象,代表连接成功的对象
    idb = e.target.result;
    // e.target.transaction属性值为一个IDBTransaction事务对象,这里是版本更新事务
    let tx = e.target.transaction;
    let name = 'Users2';
    let optionalParameters = {
      keyPath: 'userId',
      autoIncrement: false
    }

    let store = idb.createObjectStore(name, optionalParameters);
    console.log('对象仓库创建成功');

    name = 'userNameIndex';
    keyPath = 'userName';
    optionalParameters = {
      unique: false,
      mulitEntry: false
    };

    let idx = store.createIndex(name, keyPath, optionalParameters);
    console.log('索引创建成功');
  }
}

function saveData() {
  var tx = idb.transaction(['Users2'], "readwrite");
  tx.oncomplete = function() {
    console.log('保存数据成功');
    document.getElementById('btnSearchData').disabled = false;
  }
  tx.onabort = function() { console.log('保存数据失败'); }
  var store = tx.objectStore('Users2');

  var value1 = {
    userId: 1,
    userName: '用户D',
    address: '住址1'
  };
  store.put(value1);

  var value2 = {
    userId: 3,
    userName: '用户C',
    address: '住址2'
  };
  store.put(value2);

  var value3 = {
    userId: 5,
    userName: '用户B',
    address: '住址3'
  };
  store.put(value3);

  var value4 = {
    userId: 7,
    userName: '用户A',
    address: '住址4'
  };
  store.put(value4);
}

function searchData() {
  let tx = idb.transaction(['Users2'], 'readonly');
  let store = tx.objectStore('Users2');

  // 使用对象仓库的getKey方法以获取对象仓库中第一条处于搜索范围(2到10)中的数据的主键值
  let req = store.getKey(IDBKeyRange.bound(2, 10));

  req.onsuccess = function() {
    console.log('检索到一条数据,数据主键值为'+this.result);
  }

  req.onerror = function() {
    console.log('检索失败');
  }
}
</script>

最终符合搜索范围 2 到 10 之间,输出的结果是 3。
image.png

根据索引属性值检索数据

在 indexedDB API 中,也可以将对象仓库的索引属性值(将创建索引时所使用的属性值称为索引属性值)作为检索条件来检索数据。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body onload="window_onload()">
  <input type="button" value="连接数据库" onclick="connectDataBase();" />
  <input type="button" id="btnSaveData" value="保存数据" onclick="saveData();">
  <input type="button" id="btnSearchData" value="检索数据" onclick="searchData();">
</body>
</html>
<script>
// 在各浏览器中都能运行的统一定义
window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
window.IDBCursor = window.IDBCursor || window.webkitIDBCursor || window.msIDBCursor;

var dbName = 'indexedDBTest';
var dbVersion = 20150306;
var idb;

function window_onload(){
  document.getElementById('btnSaveData').disabled = true;
  document.getElementById('btnSearchData').disabled = true;
}

function connectDataBase() {
  var dbConnect = indexedDB.open(dbName, dbVersion);
  dbConnect.onsuccess = function(e) {
    idb = e.target.result;                // 引用IDBDatabase对象
    console.log('数据库连接成功');
    document.getElementById('btnSaveData').disabled = false;
  }
  dbConnect.onerror = function() {
    console.log('数据库连接失败');
  }
  dbConnect.onupgradeneeded = function(e) {
    idb = e.target.result;  

    let name = 'newUsers';
    let optionalParameters = {
      keyPath: 'userId',
      autoIncrement: false
    }

    let store = idb.createObjectStore(name, optionalParameters);
    console.log('对象仓库创建成功');

    var indexName = 'userNameIndex';
    var keyPath = 'userName';
    var indexParameters = {
      unique: false,
      multiEntry: false
    };

    // 调用对象仓库的createIndex方法创建索引
    var idx = store.createIndex(indexName, keyPath, indexParameters);
    console.log('索引创建成功');
  }
}

function saveData() {
  var tx = idb.transaction(['newUsers'], "readwrite");
  tx.oncomplete = function() {
    console.log('保存数据成功');
    document.getElementById('btnSearchData').disabled = false;
  }
  tx.onabort = function() { console.log('保存数据失败'); }
  var store = tx.objectStore('newUsers');

  var value1 = {
    userId: 1,
    userName: '用户D',
    address: '住址1'
  };
  store.put(value1);

  var value2 = {
    userId: 2,
    userName: '用户C',
    address: '住址2'
  };
  store.put(value2);

  var value3 = {
    userId: 3,
    userName: '用户B',
    address: '住址3'
  };
  store.put(value3);

  var value4 = {
    userId: 4,
    userName: '用户A',
    address: '住址4'
  };
  store.put(value4);
}

function searchData() {
  let tx = idb.transaction(['newUsers'], 'readonly');
  let store = tx.objectStore('newUsers');

  // 通过游标来检索userNameIndex索引所使用的userName属性值为用户A到D的数据
  var idx = store.index('userNameIndex');
  var range = IDBKeyRange.bound('用户A', '用户D');
  var direction = "next";

  let req = idx.openCursor(range, direction);

  req.onsuccess = function() {
    var cursor = this.result;
    if(cursor) {
      console.log('检索到一条数据,数据主键值为'+cursor.value.userName);
      cursor.continue();
    } else {
      console.log('检索结束');
    }
  }

  req.onerror = function() {
    console.log('检索数据失败');
  }
}
</script>

image.png

获取所有数据

在 indexedDB API 中,可以使用对象仓库的 getAll() 方法获取对象仓库中的所有数据。
在 indexedDB API 2.0 中,允许为对象仓库的 getAll(range, count) 方法指定数据范围及抽取数据条数。range 是一个 IBDKeyRange 对象,用于指定检索范围,count 是一个整数值,用于指定抽取数据条数。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body onload="window_onload()">
  <input type="button" value="连接数据库" onclick="connectDataBase();" />
  <input type="button" id="btnSaveData" value="保存数据" onclick="saveData();">
  <input type="button" id="btnSearchData" value="检索数据" onclick="searchData();">
</body>
</html>
<script>
// 在各浏览器中都能运行的统一定义
window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
window.IDBCursor = window.IDBCursor || window.webkitIDBCursor || window.msIDBCursor;

var dbName = 'indexedDBTest';
var dbVersion = 20150306;
var idb;

function window_onload(){
  document.getElementById('btnSaveData').disabled = true;
  document.getElementById('btnSearchData').disabled = true;
}

function connectDataBase() {
  var dbConnect = indexedDB.open(dbName, dbVersion);
  dbConnect.onsuccess = function(e) {
    idb = e.target.result;                // 引用IDBDatabase对象
    console.log('数据库连接成功');
    document.getElementById('btnSaveData').disabled = false;
  }
  dbConnect.onerror = function() {
    console.log('数据库连接失败');
  }
  dbConnect.onupgradeneeded = function(e) {
    idb = e.target.result;  

    let name = 'Users';
    let optionalParameters = {
      keyPath: 'userId',
      autoIncrement: false
    }

    let store = idb.createObjectStore(name, optionalParameters);
    console.log('对象仓库创建成功');
  }
}

function saveData() {
  var tx = idb.transaction(['Users'], "readwrite");
  tx.oncomplete = function() {
    console.log('保存数据成功');
    document.getElementById('btnSearchData').disabled = false;
  }
  tx.onabort = function() { console.log('保存数据失败'); }
  var store = tx.objectStore('Users');

  var value1 = {
    userId: 1,
    userName: '张三',
    address: '住址1'
  };
  store.put(value1);

  var value2 = {
    userId: 2,
    userName: '李四',
    address: '住址2'
  };
  store.put(value2);

  var value3 = {
    userId: 3,
    userName: '王五',
    address: '住址3'
  };
  store.put(value3);

  var value4 = {
    userId: 4,
    userName: '赵六',
    address: '住址4'
  };
  store.put(value4);
}

function searchData() {
  let tx = idb.transaction(['Users'], 'readonly');
  let store = tx.objectStore('Users');

  // 获取对象仓库中的所有数据
  var req = store.getAll();

  // 在indexedDB API2.0中,getAll(range, count)方法可指定数据范围及抽取数据条数
  // 这里从User对象仓库中检索用户主键值从2开始的三个用户
  // let req = store.getAll(IDBKeyRange.bound(2,5), 3);

  // 也可以使用索引值index的getAll
  // let idx = store.index('userNameIndex');
  // let req = idx.getAll(IDBKeyRange.bound('用户B', '用户D'), 2);

  req.onsuccess = function() {
    var users = this.result;
    for (const user of users) {
      console.log('检索到一条数据,用户名是:' + user.userName);
    }
  }

  req.onerror = function() {
    console.log('检索数据失败');
  }
}
</script>

image.png

复合索引

除了前面说的单个索引检索外,还可以使用由多个属性组成的复合索引。下面介绍创建和使用复合索引。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body onload="window_onload()">
  <input type="button" value="连接数据库" onclick="connectDataBase();" />
  <input type="button" id="btnSaveData" value="保存数据" onclick="saveData();">
  <input type="button" id="btnSearchData" value="检索数据" onclick="searchData();">
</body>
</html>
<script>
// 在各浏览器中都能运行的统一定义
window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
window.IDBCursor = window.IDBCursor || window.webkitIDBCursor || window.msIDBCursor;

var dbName = 'indexedDBTest';
var dbVersion = 20150307;
var idb;

function window_onload(){
  document.getElementById('btnSaveData').disabled = true;
  document.getElementById('btnSearchData').disabled = true;
}

function connectDataBase() {
  var dbConnect = indexedDB.open(dbName, dbVersion);
  dbConnect.onsuccess = function(e) {
    idb = e.target.result;                // 引用IDBDatabase对象
    console.log('数据库连接成功');
    document.getElementById('btnSaveData').disabled = false;
  }
  dbConnect.onerror = function() {
    console.log('数据库连接失败');
  }
  dbConnect.onupgradeneeded = function(e) {
    idb = e.target.result;

    var tx = e.target.transaction;
    var name = 'User2';
    var optionalParameters = {
      keyPath: 'userId',
      autoIncrement: false
    };
    var store = idb.createObjectStore(name, optionalParameters);
    console.log('对象仓库创建成功');

    var indexName = 'userNameAddressIndex';
    var keyPath = ['userName', 'address'];
    var optionalParameters = {
      unique: false,
      multiEntry: false
    };

    // 使用userName和address创建复合索引
    var idx = store.createIndex(indexName, keyPath, optionalParameters);
    console.log('索引创建成功');
  }
}

function saveData() {
  var tx = idb.transaction(['User2'], "readwrite");
  tx.oncomplete = function() {
    console.log('保存数据成功');
    document.getElementById('btnSearchData').disabled = false;
  }
  tx.onabort = function() { console.log('保存数据失败'); }
  var store = tx.objectStore('User2');

  var value1 = {
    userId: 1,
    userName: '张三',
    address: '住址1'
  };
  store.put(value1);

  var value2 = {
    userId: 2,
    userName: '李四',
    address: '住址2'
  };
  store.put(value2);

  var value3 = {
    userId: 3,
    userName: '王五',
    address: '住址3'
  };
  store.put(value3);

  var value4 = {
    userId: 4,
    userName: '赵六',
    address: '住址4'
  };
  store.put(value4);
}

function searchData() {
  let tx = idb.transaction(['User2'], 'readonly');
  let store = tx.objectStore('User2');
  let idx = store.index('userNameAddressIndex');
  let req = idx.get(['赵六', '住址4']);

  req.onsuccess = function() {
    if(this.result === undefined) {
      console.log('没有符合条件的数据');
    } else {
      console.log('获取数据成功,主键值为:'+ this.result.userId);
    }
  }

  req.onerror = function() {
    console.log('检索数据失败');
  }
}
</script>

image.png

在单击“检索数据”按钮后会使用 userNameAddressIndex 复合索引在对象仓库中获取用户名为“赵六”,住址为“住址4”的数据,并将该数据的主键值输出出来。

统计对象仓库中的数据数量

使用对象仓库的 count 方法来统计对象仓库中数据的条数。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body onload="window_onload()">
  <input type="button" value="统计数据条数" onclick="countData();" />
</body>
</html>
<script>
// 在各浏览器中都能运行的统一定义
window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
window.IDBCursor = window.IDBCursor || window.webkitIDBCursor || window.msIDBCursor;

var dbName = 'indexedDBTest';
var dbVersion = 20150307;
var idb;

function countData() {
  var dbConnect = indexedDB.open(dbName, dbVersion);
  dbConnect.onsuccess = function(e) {
    idb = e.target.result;                // 引用IDBDatabase对象
    console.log('数据库连接成功');

    var tx = idb.transaction(['User2'], 'readonly');
    var store = tx.objectStore('User2');

    var req = store.count();

    req.onsuccess = function() {
      console.log('对象仓库中共有'+this.result+'条记录');
    }
  }
  dbConnect.onerror = function() {
    console.log('数据库连接失败');
  }
}
</script>

image.png

最终读取到的对象仓库 User2 的数据条数是 4 条。

列举数据库中所有对象仓库的名称

可以通过数据库对象的 objectStoreNames 属性来获取该数据库中所有对象仓库的名称,该属性值为一个字符串列表,列表中每一项为数据库中的一个对象仓库的名称。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div>
    <p>获取所有对象仓库的名称</p>
    <input type="button" value="获取对象仓库" onclick="getObjectStore();" />
    <ul id="objectStoreName"></ul>
  </div>
</body>
</html>
<script>
// 在各浏览器中都能运行的统一定义
window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
window.IDBCursor = window.IDBCursor || window.webkitIDBCursor || window.msIDBCursor;

var dbName = 'indexedDBTest';
var dbVersion = 20150307;
var idb;

function getObjectStore() {
  let dbConnect = indexedDB.open(dbName, dbVersion);
  dbConnect.onsuccess = function(e) {
    let idb = e.target.result;
    for (let objectStoreName of idb.objectStoreNames) {
      document.getElementById('objectStoreName').innerHTML += '<li>'+objectStoreName+'</li>';
    }
  }
  dbConnect.onerror = function() {
    console.log('数据库连接失败');
  }
}
</script>

image.png

列举事务中所有可访问对象仓库名称

在 indexedDB 2.0 中,可以使用事务的 objectStoreNames 属性来获取该事务中所有可访问对象仓库的名称,该属性值为一个字符串列表,列表中每一项为数据库中的一个对象仓库的名称。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div>
    <p>事务中的对象仓库</p>
    <input type="button" value="连接数据库" onclick="connectDatabase();" />
    <input type="button" value="打开数据库" onclick="openDatabase();" />
    <ul id="tranObjectStoreName"></ul>
  </div>
</body>
</html>
<script>
// 在各浏览器中都能运行的统一定义
window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
window.IDBCursor = window.IDBCursor || window.webkitIDBCursor || window.msIDBCursor;

var dbName = 'indexedDBTest';
var dbVersion = 20150308;                // 更新版本
var idb;

function connectDatabase() {
  let dbConnect = indexedDB.open(dbName, dbVersion);
  dbConnect.onsuccess = function(e) {
    idb = e.target.result;
    console.log('数据库连接成功');
  }
  dbConnect.error = function() {
    console.log('数据库连接失败');
  }
  dbConnect.onupgradeneeded = function(e) {
    idb = e.target.result;

    var name = "Users";
    var optionalParameters = {
      keyPath: 'userId',
      autoIncrement: false
    };

    var store = idb.createObjectStore(name, optionalParameters);
    console.log('对象仓库创建成功');
  }
}

function openDatabase() {
  let tx = idb.transaction(['User2', 'Users'], 'readonly');

  tx.oncomplete = function() {
    console.log('打开数据库成功');
  }
  tx.onabort = function() {
    console.log('打开数据库失败');
  }

  for (let objectStoreName of tx.objectStoreNames) {
    document.getElementById('tranObjectStoreName').innerHTML+='<li>'+objectStoreName+'</li>';
  }
}
</script>

image.png

为了测试,这里我们更新了一下版本号为20150308,并在 dbConnect.onupgradeneeded 方法中再创建了一个数据仓库 Users,点击“打开数据库”就可以获取到所有可访问对象仓库的名称。

删除对象仓库

在 indexedDB API 中,可以利用数据库对象的 idb.deleteObjectStore(objectStoreName) 方法删除该数据库中的一个对象仓库,它接收一个参数,需要被删除的对象仓库的名称。

:::info 另外,deleteObjectStore 方法必须被运行在数据库的版本更新事务中,否则将导致删除对象仓库失败,同时浏览器中显示 InvalidStateError 错误。 :::

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div>
    <p>输出对象仓库Users后剩余的对象仓库</p>
    <input type="button" value="删除对象仓库Users" onclick="deleteDatabase();" />
    <ul id="remainStoreNames"></ul>
  </div>
</body>
</html>
<script>
// 在各浏览器中都能运行的统一定义
window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
window.IDBCursor = window.IDBCursor || window.webkitIDBCursor || window.msIDBCursor;

var dbName = 'indexedDBTest';
var dbVersion = 20150309;                // 更新版本
var idb;

function deleteDatabase() {
  let dbConnect = indexedDB.open(dbName, dbVersion);

  dbConnect.onupgradeneeded = function(e) {
    let idb = e.target.result;
    idb.deleteObjectStore('Users');
    for (const objectStoreName of idb.objectStoreNames) {
      document.getElementById('remainStoreNames').innerHTML += '<li>'+objectStoreName+'</li>';
    }
  }

  dbConnect.onerror = function() {
    console.log('数据库连接失败');
  }
}
</script>

image.png

根据主键删除单条数据

在 indexedDB API 中,可利用对象仓库的 delete(key) 方法从对象仓库中删除一条数据。它接收一个参数,代表要删除数据的主键值,可以为任何类型。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body onload="window_onload()">
  <input type="button" value="连接数据库" onclick="connectDataBase();" />
  <input type="button" id="btnSaveData" value="保存数据" onclick="saveData();" />
  <input type="button" id="btnSearchData" value="检索数据" onclick="searchData();" />
  <input type="button" id="deleteData" value="删除数据" onclick="deleteData();" />
</body>
</html>
<script>
// 在各浏览器中都能运行的统一定义
window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
window.IDBCursor = window.IDBCursor || window.webkitIDBCursor || window.msIDBCursor;

var dbName = 'indexedDBTest';
var dbVersion = 20150309;
var idb;

function window_onload(){
  document.getElementById('btnSaveData').disabled = true;
  document.getElementById('btnSearchData').disabled = true;
  document.getElementById('deleteData').disabled = true;
}

function connectDataBase() {
  var dbConnect = indexedDB.open(dbName, dbVersion);
  dbConnect.onsuccess = function(e) {
    idb = e.target.result;                // 引用IDBDatabase对象
    console.log('数据库连接成功');
    document.getElementById('btnSaveData').disabled = false;
  }
  dbConnect.onerror = function() {
    console.log('数据库连接失败');
  }
  // dbConnect.onupgradeneeded = function(e) {
  //   idb = e.target.result;

  //   var tx = e.target.transaction;
  //   var name = 'User2';
  //   var optionalParameters = {
  //     keyPath: 'userId',
  //     autoIncrement: false
  //   };
  //   var store = idb.createObjectStore(name, optionalParameters);
  //   console.log('对象仓库创建成功');

  //   var indexName = 'userNameAddressIndex';
  //   var keyPath = ['userName', 'address'];
  //   var optionalParameters = {
  //     unique: false,
  //     multiEntry: false
  //   };

  //   var idx = store.createIndex(indexName, keyPath, optionalParameters);
  //   console.log('索引创建成功');
  // }
}

function saveData() {
  var tx = idb.transaction(['User2'], "readwrite");
  tx.oncomplete = function() {
    console.log('保存数据成功');
    document.getElementById('btnSearchData').disabled = false;
    document.getElementById('deleteData').disabled = false;
  }
  tx.onabort = function() { console.log('保存数据失败'); }
  var store = tx.objectStore('User2');

  var value1 = {
    userId: 1,
    userName: '张三',
    address: '住址1'
  };
  store.put(value1);

  var value2 = {
    userId: 2,
    userName: '李四',
    address: '住址2'
  };
  store.put(value2);

  var value3 = {
    userId: 3,
    userName: '王五',
    address: '住址3'
  };
  store.put(value3);

  var value4 = {
    userId: 4,
    userName: '赵六',
    address: '住址4'
  };
  store.put(value4);
}

function searchData() {
  let tx = idb.transaction(['User2'], 'readonly');
  let store = tx.objectStore('User2');
  let range = IDBKeyRange.bound(1,4);
  let direction = 'next';

  let req = store.openCursor(range, direction);

  req.onsuccess = function() {
    let cursor = this.result;
    if(cursor) {
      console.log('检索到一条数据,用户名是:'+cursor.value.userName);
      cursor.continue();
    } else {
      console.log('检索结束');
    }
  }

  req.onerror = function() {
    console.log('检索数据失败');
  }
}

function deleteData() {
  let tx = idb.transaction(['User2'], 'readwrite');
  tx.oncomplete = function() {
    console.log('删除数据成功');
  }
  tx.onabort = function() {
    console.log('删除数据失败');
  }
  let store = tx.objectStore('User2');
  store.delete(1);
}
</script>

image.png

列举对象仓库的所有索引名称及删除索引

在 indexedDB API 中,可以通过一个数据仓库对象的 indexNames 属性来获取该对象仓库的所有索引名称,该属性值为一个字符串列表,列表中每一项为该对象仓库的一个索引名称。
可以利用对象仓库的 deleteIndex(IndexName) 方法删除索引。deleteIndex 方法接收一个参数,指定需要被删除的索引名称。

:::info deleteIndex 方法必须被运行在数据库的版本更新事务中,否则将导致删除索引失败,同时浏览器中显示 InvalidStateError 错误。 :::

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body onload="init()">
  <div>
    <p>删除索引</p>
    <p>User2对象仓库中的索引</p>
    <ul id="indexNames"></ul>
  </div>
</body>
</html>
<script>
// 在各浏览器中都能运行的统一定义
window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
window.IDBCursor = window.IDBCursor || window.webkitIDBCursor || window.msIDBCursor;

var dbName = 'indexedDBTest';
var dbVersion = 20150310;
var idb;

function init() {
  let dbConnect = indexedDB.open(dbName, dbVersion);
  dbConnect.onsuccess = function(e) {
    let idb = e.target.result;
  }
  dbConnect.onerror = function() {
    console.log('数据库连接失败');
  }
  dbConnect.onupgradeneeded = function(e) {
    let idb = e.target.result;
    let tx = e.target.transaction;
    let store = tx.objectStore('User2');
    store.deleteIndex('userNameAddressIndex');        // 删除对象仓库的索引
    for (let indexName of store.indexNames) {
      document.getElementById('indexNames').innerHTML+='<li>'+indexName+'</li>';
    }
  }
}
</script>

image.png

删除后,原来在 User2 对象仓库下的 “userNameAddressIndex”索引就没了。

使用索引对象的方法

在 IndexedDB API 中,索引对象的方法:getKeyopenKeyCursorcount 方法。

getKey

根据指定的索引属性值获取某条数据的主键值,该主键值所在数据的索引属性值为指定的索引属性值;
getKey 接收一个参数,用于指定被检索数据的索引属性值。

:::info 如果没有使用索引对象的 unique 属性值将索引定义为唯一索引,则在对象仓库中可能存在多条符合条件的数据,但是使用索引的 getKey 方法时将只能获取到第一条符合条件的数据主键值。 :::

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body onload="window_onload();">
  <input type="button" value="连接数据库" onclick="connectDatabase();" />
  <input type="button" value="保存数据" id="btnSaveData" onclick="saveData();" />
  <input type="button" value="检索数据" id="btnSearchData" onclick="searchData();" />
</body>
</html>
<script>
// 在各浏览器中都能运行的统一定义
window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
window.IDBCursor = window.IDBCursor || window.webkitIDBCursor || window.msIDBCursor;

var dbName = 'indexedDBTest';
var dbVersion = 20150312;
var idb;

function window_onload() {
  document.getElementById('btnSaveData').disabled = true;
  document.getElementById('btnSearchData').disabled = true;
}

function connectDatabase() {
  let dbConnect = indexedDB.open(dbName, dbVersion);
  dbConnect.onsuccess = function(e) {
    idb = e.target.result;
    console.log('数据库连接成功');
    document.getElementById('btnSaveData').disabled = false;
  }
  dbConnect.onerror = function() {
    console.log('数据库连接失败');
  }
  dbConnect.onupgradeneeded = function(e) {
    let idb = e.target.result;
    let name = 'Users4';
    let optionParameters = {
      keyPath: 'userId',
      autoIncrement: false
    };

    let store = idb.createObjectStore(name, optionParameters);
    console.log('对象仓库创建成功');

    name = 'userNameIndex';
    let keyPath = 'userName';
    optionParameters = {
      unique: false,
      multiEntry: false
    };
    let idx = store.createIndex(name, keyPath, optionParameters);
    console.log('索引创建成功');
  }
}

function saveData() {
  let tx = idb.transaction(['Users4'], 'readwrite');
  tx.oncomplete = function() {
    console.log('保存数据成功');
    document.getElementById('btnSearchData').disabled = false;
  }
  tx.onabort = function() {
    console.log('保存数据失败');
  }

  let store = tx.objectStore('Users4');
  let value = {
    userId: 1,
    userName: '用户D',
    address: '住址1'
  };
  store.put(value);

  value = {
    userId: 2,
    userName: '用户C',
    address: '住址2'
  };
  store.put(value);

  value = {
    userId: 3,
    userName: '用户B',
    address: '住址3'
  };
  store.put(value);

  value = {
    userId: 4,
    userName: '用户A',
    address: '住址4'
  };
  store.put(value);
}

function searchData() {
  let tx = idb.transaction(['Users4'], 'readwrite');
  let store = tx.objectStore('Users4');
  let idx = store.index('userNameIndex');
  let req = idx.getKey('用户B');            // 根据指定的索引属性值获取某条数据的主键值
  req.onsuccess = function() {
    console.log('检索到一条数据,数据主键值:'+this.result);
  }
  req.onerror = function() {
    conosle.log('检索数据失败');
  }
}
</script>

image.png当单击“检索数据”按钮时,会在 Users4 对象仓库中检索 userName 属性值为“用户B”的数据主键值。

getAllKeys

使用索引对象的方法获取所有数据的主键值并按指定的索引属性值进行排序。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body onload="window_onload();">
  <input type="button" value="连接数据库" onclick="connectDatabase();" />
  <input type="button" value="保存数据" id="btnSaveData" onclick="saveData();" />
  <input type="button" value="检索数据" id="btnSearchData" onclick="searchData();" />
</body>
</html>
<script>
// 在各浏览器中都能运行的统一定义
window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
window.IDBCursor = window.IDBCursor || window.webkitIDBCursor || window.msIDBCursor;

var dbName = 'indexedDBTest';
var dbVersion = 20150312;
var idb;

function window_onload() {
  document.getElementById('btnSaveData').disabled = true;
  document.getElementById('btnSearchData').disabled = true;
}

function connectDatabase() {
  let dbConnect = indexedDB.open(dbName, dbVersion);
  dbConnect.onsuccess = function(e) {
    idb = e.target.result;
    console.log('数据库连接成功');
    document.getElementById('btnSaveData').disabled = false;
  }
  dbConnect.onerror = function() {
    console.log('数据库连接失败');
  }
  dbConnect.onupgradeneeded = function(e) {
    let idb = e.target.result;

    let name = 'Users4';
    let optionParameters = {
      keyPath: 'userId',
      autoIncrement: false
    };

    let store = idb.createObjectStore(name, optionParameters);
    console.log('对象仓库创建成功');

    name = 'userNameIndex';
    let keyPath = 'userName';
    optionParameters = {
      unique: false,
      multiEntry: false
    };
    let idx = store.createIndex(name, keyPath, optionParameters);
    console.log('索引创建成功');
  }
}

function saveData() {
  let tx = idb.transaction(['Users4'], 'readwrite');
  tx.oncomplete = function() {
    console.log('保存数据成功');
    document.getElementById('btnSearchData').disabled = false;
  }
  tx.onabort = function() {
    console.log('保存数据失败');
  }

  let store = tx.objectStore('Users4');
  let value = {
    userId: 1,
    userName: '用户D',
    address: '住址1'
  };
  store.put(value);

  value = {
    userId: 2,
    userName: '用户C',
    address: '住址2'
  };
  store.put(value);

  value = {
    userId: 3,
    userName: '用户B',
    address: '住址3'
  };
  store.put(value);

  value = {
    userId: 4,
    userName: '用户A',
    address: '住址4'
  };
  store.put(value);
}

function searchData() {
  let tx = idb.transaction(['Users4'], 'readonly');
  let store = tx.objectStore('Users4');
  let idx = store.index('userNameIndex');
  let req = idx.getAllKeys();

  req.onsuccess = function () {
    let users = this.result;
    for (let user of users) {
      console.log('检索到一条数据,主键值为'+user);
    }
  }

  req.onerror = function() {
    console.log('检索数据失败');
  }
}
</script>

image.png

当点击“数据检索”时,会获取所有数据的主键值,并按指定的索引属性值(这里是根据 userName)进行排序。

在 IndexedDB API 2.0 版本中,允许为索引的 getAllKeys 方法指定数据范围及抽取数据条数。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body onload="window_onload();">
  <input type="button" value="连接数据库" onclick="connectDatabase();" />
  <input type="button" value="保存数据" id="btnSaveData" onclick="saveData();" />
  <input type="button" value="检索数据" id="btnSearchData" onclick="searchData();" />
</body>
</html>
<script>
// 在各浏览器中都能运行的统一定义
window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
window.IDBCursor = window.IDBCursor || window.webkitIDBCursor || window.msIDBCursor;

var dbName = 'indexedDBTest';
var dbVersion = 20150312;
var idb;

function window_onload() {
  document.getElementById('btnSaveData').disabled = true;
  document.getElementById('btnSearchData').disabled = true;
}

function connectDatabase() {
  let dbConnect = indexedDB.open(dbName, dbVersion);
  dbConnect.onsuccess = function(e) {
    idb = e.target.result;
    console.log('数据库连接成功');
    document.getElementById('btnSaveData').disabled = false;
  }
  dbConnect.onerror = function() {
    console.log('数据库连接失败');
  }
  dbConnect.onupgradeneeded = function(e) {
    let idb = e.target.result;

    let name = 'Users4';
    let optionParameters = {
      keyPath: 'userId',
      autoIncrement: false
    };

    let store = idb.createObjectStore(name, optionParameters);
    console.log('对象仓库创建成功');

    name = 'userNameIndex';
    let keyPath = 'userName';
    optionParameters = {
      unique: false,
      multiEntry: false
    };
    let idx = store.createIndex(name, keyPath, optionParameters);
    console.log('索引创建成功');
  }
}

function saveData() {
  let tx = idb.transaction(['Users4'], 'readwrite');
  tx.oncomplete = function() {
    console.log('保存数据成功');
    document.getElementById('btnSearchData').disabled = false;
  }
  tx.onabort = function() {
    console.log('保存数据失败');
  }

  let store = tx.objectStore('Users4');
  let value = {
    userId: 1,
    userName: '用户D',
    address: '住址1'
  };
  store.put(value);

  value = {
    userId: 2,
    userName: '用户C',
    address: '住址2'
  };
  store.put(value);

  value = {
    userId: 3,
    userName: '用户B',
    address: '住址3'
  };
  store.put(value);

  value = {
    userId: 4,
    userName: '用户A',
    address: '住址4'
  };
  store.put(value);
}

function searchData() {
  let tx = idb.transaction(['Users4'], 'readonly');
  let store = tx.objectStore('Users4');
  let idx = store.index('userNameIndex');
  let req = idx.getAllKeys(IDBKeyRange.bound('用户B', '用户D'), 2);    // 指定数据范围及抽取数据条数

  req.onsuccess = function () {
    let users = this.result;
    for (let user of users) {
      console.log('检索到一条数据,主键值为'+user);
    }
  }

  req.onerror = function() {
    console.log('检索数据失败');
  }
}
</script>

image.png

openKeyCursor

用于对索引进行检索,该方法的使用方法与索引对象的 openCursor 方法的使用方法类似。openKeyCursor(range, direction) 中的两个参数用法与 openCursor 一致,这里不再赘述。
同样地,openKeyCursor 会创建一个游标,可以在 onsuccess 事件中通过 IDBRequest 对象的 result 属性值访问该游标。该游标包含所有被检索数据的索引属性值,可以通过游标对象的 key 属性获取。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body onload="window_onload();">
  <input type="button" value="连接数据库" onclick="connectDatabase();" />
  <input type="button" value="保存数据" id="btnSaveData" onclick="saveData();" />
  <input type="button" value="检索数据" id="btnSearchData" onclick="searchData();" />
</body>
</html>
<script>
// 在各浏览器中都能运行的统一定义
window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
window.IDBCursor = window.IDBCursor || window.webkitIDBCursor || window.msIDBCursor;

var dbName = 'indexedDBTest';
var dbVersion = 20150312;
var idb;

function window_onload() {
  document.getElementById('btnSaveData').disabled = true;
  document.getElementById('btnSearchData').disabled = true;
}

function connectDatabase() {
  let dbConnect = indexedDB.open(dbName, dbVersion);
  dbConnect.onsuccess = function(e) {
    idb = e.target.result;
    console.log('数据库连接成功');
    document.getElementById('btnSaveData').disabled = false;
  }
  dbConnect.onerror = function() {
    console.log('数据库连接失败');
  }
  dbConnect.onupgradeneeded = function(e) {
    let idb = e.target.result;

    let name = 'Users4';
    let optionParameters = {
      keyPath: 'userId',
      autoIncrement: false
    };

    let store = idb.createObjectStore(name, optionParameters);
    console.log('对象仓库创建成功');

    name = 'userNameIndex';
    let keyPath = 'userName';
    optionParameters = {
      unique: false,
      multiEntry: false
    };
    let idx = store.createIndex(name, keyPath, optionParameters);
    console.log('索引创建成功');
  }
}

function saveData() {
  let tx = idb.transaction(['Users4'], 'readwrite');
  tx.oncomplete = function() {
    console.log('保存数据成功');
    document.getElementById('btnSearchData').disabled = false;
  }
  tx.onabort = function() {
    console.log('保存数据失败');
  }

  let store = tx.objectStore('Users4');
  let value = {
    userId: 1,
    userName: '用户D',
    address: '住址1'
  };
  store.put(value);

  value = {
    userId: 2,
    userName: '用户C',
    address: '住址2'
  };
  store.put(value);

  value = {
    userId: 3,
    userName: '用户B',
    address: '住址3'
  };
  store.put(value);

  value = {
    userId: 4,
    userName: '用户A',
    address: '住址4'
  };
  store.put(value);
}

function searchData() {
  let tx = idb.transaction(['Users4'],'readonly');
  let store = tx.objectStore('Users4');
  let idx = store.index('userNameIndex');
  let range = IDBKeyRange.lowerBound('用户A');
  let direction = 'next';
  let req = idx.openKeyCursor(range, direction);

  req.onsuccess = function() {
    let cursor = this.result;
    if(cursor) {
      console.log('索引属性值:'+cursor.key);
      cursor.continue();
    } else {
      console.log('检索结束');
    }
  }

  req.onerror = function() {
    console.log('检索数据失败');
  }
}
</script>

image.png

在 IndexedDB API 中,索引对象的 count 方法的作用为获取对象仓库中索引属性值等于指定的索引属性值的数据的条数。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body onload="window_onload();">
  <input type="button" value="连接数据库" onclick="connectDatabase();" />
  <input type="button" value="保存数据" id="btnSaveData" onclick="saveData();" />
  <input type="button" value="检索数据" id="btnSearchData" onclick="searchData();" />
</body>
</html>
<script>
// 在各浏览器中都能运行的统一定义
window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
window.IDBCursor = window.IDBCursor || window.webkitIDBCursor || window.msIDBCursor;

var dbName = 'indexedDBTest';
var dbVersion = 20150312;
var idb;

function window_onload() {
  document.getElementById('btnSaveData').disabled = true;
  document.getElementById('btnSearchData').disabled = true;
}

function connectDatabase() {
  let dbConnect = indexedDB.open(dbName, dbVersion);
  dbConnect.onsuccess = function(e) {
    idb = e.target.result;
    console.log('数据库连接成功');
    document.getElementById('btnSaveData').disabled = false;
  }
  dbConnect.onerror = function() {
    console.log('数据库连接失败');
  }
  dbConnect.onupgradeneeded = function(e) {
    let idb = e.target.result;

    let name = 'Users4';
    let optionParameters = {
      keyPath: 'userId',
      autoIncrement: false
    };

    let store = idb.createObjectStore(name, optionParameters);
    console.log('对象仓库创建成功');

    name = 'userNameIndex';
    let keyPath = 'userName';
    optionParameters = {
      unique: false,
      multiEntry: false
    };
    let idx = store.createIndex(name, keyPath, optionParameters);
    console.log('索引创建成功');
  }
}

function saveData() {
  let tx = idb.transaction(['Users4'], 'readwrite');
  tx.oncomplete = function() {
    console.log('保存数据成功');
    document.getElementById('btnSearchData').disabled = false;
  }
  tx.onabort = function() {
    console.log('保存数据失败');
  }

  let store = tx.objectStore('Users4');
  let value = {
    userId: 1,
    userName: '用户A',
    address: '住址1'
  };
  store.put(value);

  value = {
    userId: 2,
    userName: '用户B',
    address: '住址2'
  };
  store.put(value);

  value = {
    userId: 3,
    userName: '用户C',
    address: '住址3'
  };
  store.put(value);

  value = {
    userId: 4,
    userName: '用户D',
    address: '住址4'
  };
  store.put(value);
}

function searchData() {
  let tx = idb.transaction(['Users4'],'readonly');
  let store = tx.objectStore('Users4');
  let idx = store.index('userNameIndex');
  let req = idx.count('用户A');

  req.onsuccess = function() {
    console.log('共存在'+this.result+'条索引属性值为用户A的数据');
  }

  req.onerror = function() {
    console.log('检索数据失败');
  }
}
</script>

image.png

在指定范围中抽取数据主键

在 IndexedDB 2.0 中,提供对象仓库的 openKeyCursor 方法,用于在一个指定范围中进行遍历来返回数据主键,而不是在整个对象仓库中进行遍历。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body onload="window_onload();">
  <input type="button" value="连接数据库" onclick="connectDatabase();" />
  <input type="button" value="保存数据" id="btnSaveData" onclick="saveData();" />
  <input type="button" value="检索数据" id="btnSearchData" onclick="searchData();" />
</body>
</html>
<script>
// 在各浏览器中都能运行的统一定义
window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
window.IDBCursor = window.IDBCursor || window.webkitIDBCursor || window.msIDBCursor;

var dbName = 'indexedDBTest';
var dbVersion = 20150313;
var idb;

function window_onload() {
  document.getElementById('btnSaveData').disabled = true;
  document.getElementById('btnSearchData').disabled = true;
}

function connectDatabase() {
  let dbConnect = indexedDB.open(dbName, dbVersion);
  dbConnect.onsuccess = function(e) {
    idb = e.target.result;
    console.log('数据库连接成功');
    document.getElementById('btnSaveData').disabled = false;
  }
  dbConnect.onerror = function() {
    console.log('数据库连接失败');
  }
  dbConnect.onupgradeneeded = function(e) {
    idb = e.target.result;

    let tx = e.target.transaction;
    let name = 'Users5';
    let optionalParameters = {
      keyPath: 'userId',
      autoIncrement: false
    };

    let store = idb.createObjectStore(name, optionalParameters);
    console.log('对象仓库创建成功');

    name = 'userNameIndex';
    let keyPath = 'userName';
    optionalParameters = {
      unique: false,
      multiEntry: false
    };
    let idx = store.createIndex(name, keyPath, optionalParameters);
    console.log('索引创建成功');
  }
}

function saveData() {
  let tx = idb.transaction(['Users5'], 'readwrite');
  tx.oncomplete = function() {
    console.log('保存数据成功');
    document.getElementById('btnSearchData').disabled = false;
  }
  tx.onabort = function() {
    console.log('保存数据失败');
  }

  let store = tx.objectStore('Users5');
  let value = {
    userId: 1,
    userName: '用户D',
    address: '住址1'
  };
  store.put(value);

  value = {
    userId: 3,
    userName: '用户C',
    address: '住址2'
  };
  store.put(value);

  value = {
    userId: 5,
    userName: '用户B',
    address: '住址3'
  };
  store.put(value);

  value = {
    userId: 7,
    userName: '用户A',
    address: '住址4'
  };
  store.put(value);
}

// 获取用户ID在2到6之间的所有数据的用户ID
function searchData() {
  let tx = idb.transaction(['Users5'],'readonly');
  let store = tx.objectStore('Users5');
  let range = IDBKeyRange.bound(2, 6);
  let direction = 'next';
  let req = store.openKeyCursor(range, direction);

  req.onsuccess = function() {
    let cursor = this.result;
    if(cursor) {
      console.log('检索到一条数据,用户ID为' + cursor.key);
      cursor.continue();
    }
  }

  req.onerror = function() {
    console.log('检索数据失败');
  }
}
</script>

image.png

使用游标对象的方法

下面介绍一下 indexedDB API 中游标对象的 continue 方法与 advance 方法。

游标对象的 continue 方法是重用创建该游标对象时所调用的 IDBRequest 对象,再次向数据库发出获取符合检索条件的游标数据的请求。

:::info 在 onsuccess 函数中,第一次时,事件对象的 result 就是游标对象,它的 key \ value 分别代表第一条数据的主键和数据对象,在被重用的 IDBRequest 对象的 onsuccess 事件函数中,游标对象的 key \ value 分别代表游标数据中下一条数据的主键或数据对象。 :::

continue 方法可以传入一个参数 key,方法执行后在被重用的 IDBRequest 对象的 onsuccess 事件函数中,游标数据为数据主键或索引属性值等于参数 key 的参数值的数据。

游标对象的 advance 方法与游标对象的 continue 方法的作用大致相同,均为使用对象仓库的 openCursor 方法、索引对象的 openCursor 方法或索引对象的 openKeyCursor 方法检索一批数据后对数据进行遍历。

advance 使用一个参数,代表在检索到的所有数据中需要获取的下一条数据与当前游标数据之间相隔多少条数据。例如当前游标数据为检索到的所有数据中的第一条数据,advance 方法的参数值为 3,则下一条游标数据为检索到的所有数据中的第 4 条数据。如果该 number 参数的数值太大,例如检索到的数据条数为 10 条,而 advance 方法的参数值为 11,浏览器不抛出异常,检索正常结束。

function searchData() {
  let tx = idb.transaction(['Users5'],'readonly');
  let store = tx.objectStore('Users5');
  let range = IDBKeyRange.bound(1, 4);
  let direction = 'next';
  let req = store.openCursor(range, direction);

  req.onsuccess = function() {
    let cursor = this.result;
    if(cursor) {
      console.log('检索到一条数据,用户名为'+cursor.value.userName);
      if(cursor.value.userId == '1') {
        cursor.advance(2);
      } else {
        cursor.continue();
      }
    } else {
      console.log('检索结束');
    }
  }

  req.onerror = function() {
    console.log('检索数据失败');
  }
}

to be continue…