javascript 操作浏览器数据库IndexedDB

前言

Indexed Database API 简称 IndexedDB,是浏览器中存储结构化数据的一个方案。IndexedDB 用于代

替目前已废弃的 Web SQL Database API。IndexedDB 背后的思想是创造一套 API,方便 JavaScript 对象的

存储和获取,同时也支持查询和搜索。

IndexedDB 的设计几乎完全是异步的。为此,大多数操作以请求的形式执行,这些请求会异步执行,

产生成功的结果或错误。绝大多数 IndexedDB 操作要求添加 onerror 和 onsuccess 事件处理程序来确

定输出。

2017 年,新发布的主流浏览器(Chrome、Firefox、Opera、Safari)完全支持 IndexedDB。IE10/11

和 Edge 浏览器部分支持 IndexedDB。

特点

  • 1 键值对储存。 IndexedDB 内部采用对象仓库(object store)存放数据。所有类型的数据都可以直接存入,包括 JavaScript 对象。对象仓库中,数据以"键值对"的形式保存,每一个数据记录都有对应的主键,主键是独一无二的,不能有重复,否则会抛出一个错误。
  • 2 异步。 IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,这与 LocalStorage 形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。
  • 3 支持事务。 IndexedDB 支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。
  • 4 同源限制 IndexedDB 受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。
  • 5 储存空间大 IndexedDB 的储存空间比 LocalStorage 大得多,一般来说不少于 250MB,甚至没有上限。
  • 6 支持二进制储存。 IndexedDB 不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer 对象和 Blob 对象)。

数据库

IndexedDB 是类似于 MySQL 或 Web SQL Database 的数据库。与传统数据库最大的区别在于,

IndexedDB 使用对象存储而不是表格保存数据。IndexedDB 数据库就是在一个公共命名空间下的一组对

象存储,类似于 NoSQL 风格的实现。

使用 IndexedDB 数据库的第一步是调用 indexedDB.open()方法,并给它传入一个要打开的数据

库名称。如果给定名称的数据库已存在,则会发送一个打开它的请求;如果不存在,则会发送创建并打

开这个数据库的请求。这个方法会返回 IDBRequest 的实例,可以在这个实例上添加 onerror 和

onsuccess 事件处理程序

代码语言:js
复制
let db,
 request,
 version = 1;
request = indexedDB.open("admin", version);
request.onerror = (event) =>
 alert(`Failed to open: ${event.target.errorCode}`);
request.onsuccess = (event) => {
 db = event.target.result;
}; 

在两个事件处理程序中,event.target 都指向 request,因此使用哪个都可以。如果 onsuccess

事件处理程序被调用,说明可以通过 event.target.result 访问数据库(IDBDatabase)实例了,

这个实例会保存到 db 变量中。之后,所有与数据库相关的操作都要通过 db 对象本身来进行。如果打

开数据库期间发生错误,event.target.errorCode 中就会存储表示问题的错误码。

对象存储

建立了数据库连接之后,下一步就是使用对象存储。如果数据库版本与期待的不一致,那可能需要

创建对象存储。不过,在创建对象存储前,有必要想一想要存储什么类型的数据。

假设要存储包含用户名、密码等内容的用户记录。可以用如下对象来表示一条记录:

代码语言:js
复制
let user = {
 username: "007",
 firstName: "James",
 lastName: "Bond",
 password: "foo"
}; 

观察这个对象,可以很容易看出最适合作为对象存储键的 username 属性。用户名必须全局唯一,

它也是大多数情况下访问数据的凭据。这个键很重要,因为创建对象存储时必须指定一个键。

数据库的版本决定了数据库模式,包括数据库中的对象存储和这些对象存储的结构。如果数据库还

不存在,open()操作会创建一个新数据库,然后触发 upgradeneeded 事件。可以为这个事件设置处

理程序,并在处理程序中创建数据库模式。如果数据库存在,而你指定了一个升级版的版本号,则会立

即触发 upgradeneeded 事件,因而可以在事件处理程序中更新数据库模式。

代码语言:js
复制
request.onupgradeneeded = (event) => {
 const db = event.target.result;
 // 如果存在则删除当前 objectStore。测试的时候可以这样做
 // 但这样会在每次执行事件处理程序时删除已有数据
 if (db.objectStoreNames.contains("users")) {
 db.deleteObjectStore("users");
 }
 db.createObjectStore("users", { keyPath: "username" });
};

这里第二个参数的 keyPath 属性表示应该用作键的存储对象的属性名。

事务

创建了对象存储之后,剩下的所有操作都是通过事务完成的。事务要通过调用数据库对象的

transaction()方法创建。任何时候,只要想要读取或修改数据,都要通过事务把所有修改操作组织

起来,

注意request.onsuccess和 request.onupgradeneeded 都是异步得,所以下方得增删改查 都需要 在异步里操作,不然回报找不到 db.transaction

代码语言:js
复制
  let db;
  var request = indexedDB.open('users');
  request.onerror = function (event) {
    console.log('数据库打开报错');
  };
  request.onsuccess = function (event) {
    db = event.target.result;
  };
  request.onupgradeneeded = function(event) {
    db = event.target.result;
    var objectStore;
    if (!db.objectStoreNames.contains('person')) {
      objectStore = db.createObjectStore('person', { keyPath: 'id' });
    }else{
      objectStore = db.createObjectStore('person', { keyPath: 'id' }); 
    }
    objectStore.createIndex('name', 'name', { unique: false }); 
    objectStore.createIndex('age', 'age', { unique: false }); 
  };

写操作

代码语言:js
复制
 function addData() {
    console.log(db)
    var request = db.transaction(['person'], 'readwrite') //readwrite表示有读写权限
      .objectStore('person')
      .add({ id: 1, name: 'iwhao', age: 18}); //新增数据
    request.onsuccess = function (event) {
      console.log('数据写入成功');
    };
    request.onerror = function (event) {
      console.log('数据写入失败');
    }
  }

读取操作

代码语言:js
复制
function read() {
   var transaction = db.transaction(['person']);
   var objectStore = transaction.objectStore('person');
   var request = objectStore.get(1);
   request.onerror = function(event) {
     console.log('事务失败');
   };
   request.onsuccess = function( event) {
      if (request.result) {
        console.log(request.result);
      } else {
        console.log('未获得数据记录');
      }
   };
}

更新

代码语言:js
复制
  function upData() {
    console.log(db)
    var request = db.transaction(['person'], 'readwrite') //readwrite表示有读写权限
      .objectStore('person')
      .put({ id: 1, name: 'whao', age: 20}); //更新数据
    request.onsuccess = function (event) {
      console.log('数据写入成功');
    };
    request.onerror = function (event) {
      console.log('数据写入失败');
    }
  }

删除

代码语言:js
复制
function del(){
    var request = db.transaction(['person'], 'readwrite')
      .objectStore('person')
      .delete(1);
    request.onsuccess = function (event) {
      console.log('数据删除成功');
    };
}

索引

对某些数据集,可能需要为对象存储指定多个键。例如,如果同时记录了用户 ID 和用户名,那可能

需要通过任何一种方式来获取用户数据。为此,可以考虑将用户 ID 作为主键,然后在用户名上创建索引。

假定新建表格的时候,对name字段建立了索引。

代码语言:js
复制
objectStore.createIndex('name', 'name', { unique: false });

createIndex()的第一个参数是索引的名称,第二个参数是索引属性的名称,第三个参数是包含

键 unique 的 options 对象。这个选项中的 unique 应该必须指定,表示这个键是否在所有记录中唯

一。因为 username 可能不会重复,所以这个键是唯一的。

索引查询

代码语言:js
复制
function getIndexes() {
    const transaction = db.transaction(['person']);
    const store = transaction.objectStore('person');
    const index = store.index("name")
    const request = index.get("iwhao");
    request.onerror = function(event) {
      console.log('事务失败');
    };
    request.onsuccess = function( event) {
      if (request.result) {
        console.log(request.result);
      } else {
        console.log('未获得数据记录');
      }
    };
  }

限制

IndexedDB 的很多限制实际上与 Web Storage 一样。首先,IndexedDB 数据库是与页面源(协议、域

和端口)绑定的,因此信息不能跨域共享。这意味着 www.wrox.com 和 p2p.wrox.com 会对应不同的数据

存储。

其次,每个源都有可以存储的空间限制。当前 Firefox 的限制是每个源 50MB,而 Chrome 是 5MB。

移动版 Firefox 有 5MB 限制,如果用度超出配额则会请求用户许可。

Firefox 还有一个限制——本地文本不能访问 IndexedDB 数据库。Chrome 没有这个限制。因此在本

地运行本书示例时,要使用 Chrome。