浅析浏览器数据库IndexedDB:基本概念、操作流程及各对象属性和方法

一、概述

  随着浏览器的功能不断增强,越来越多的网站开始考虑,将大量数据储存在客户端,这样可以减少从服务器获取数据,直接从本地获取数据。

  Cookie 的大小不超过4KB,且每次请求都会发送回服务器;

  LocalStorage 在 2.5MB 到 10MB 之间(各家浏览器不同),而且不提供搜索功能,不能建立自定义的索引。

  所以,需要一种新的解决方案,这就是 IndexedDB 诞生的背景。

  IndexedDB 就是浏览器提供的本地数据库,它可以被网页脚本创建和操作。IndexedDB 允许储存大量数据,提供查找接口,还能建立索引,就数据库类型而言,IndexedDB 不属于关系型数据库(不支持 SQL 查询语句)。

  indexedDB具有以下特点:

1、键值对存储。IndexedDB内部采用对象仓库(object store)存放数据,所有类型的数据都可以直接存入,包括JavaScript对象。对象仓库中,数据以“键值对”的形式保存,每一个数据记录都有对应的主键,主键是独一无二的,不能有重复,否则会报错。

2、异步。IndexedDB操作时不会锁死浏览器,用户依然可以进行其他操作,但LocalStorage的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。

3、支持事务。IndexedDB支持事务,所以一系列操作步骤中,只要有一步失败,这个事务就都取消,数据库回滚到事务发生之前的状态。

4、同源限制。IndexedDB受到同源限制,每一个数据库对应创建他们的域名。网页只能访问自身名下的数据库,二不能访问跨域的数据库。

5、储存空间大。一般来说不少于250MB,甚至没有上限。

6、支持二进制储存。IndexedDB不仅可以存储字符串,还可以储存二进制数据。

二、基本概念

  IndexedDB是一个比较复杂的API,它把不同的实体,抽象成一个个对象接口。

  • 数据库:IDBDatabase 对象
  • 对象仓库:IDNObjectStore 对象
  • 索引:IDBIndex 对象
  • 事务:IDBTransaction 对象
  • 操作请示:IDBRequest 对象
  • 指针:IDBCursor 对象
  • 主键集合:IDBKeyRange 对象

1、数据库:数据库是一系列相关数据的容器。每个域名(协议+域名+端口号)都可以新建任意多个数据库。

  IndexedDB数据库有版本的概念。同一个时刻,只能有一个版本的数据库存在。如果要修改数据库结构(新增或删除表、索引或者主键),只能通过升级数据库版本完成。

2、对象仓库:每个数据库包含若干个对象仓库(object store)。它类似于关系型数据库的表格。

3、数据记录:对象仓库保存的是数据记录。每条记录类似于关系型数据库的行,但是只有主键和数据体两部分。主键用来建立默认的索引,必须是不同的,否则会报错。主键可以是数据记录里面的一个属性,也可以指定为一个递增的整数编号。

{ id: 1, text: 'foo' }

  上面的对象中,id属性可以当作主键。数据体可以是任意数据类型,不限于对象。

4、索引:为了加速数据的检索,可以在对象仓库里面,为不同的属性建立索引。

5、事务:数据记录的读写和删改,都要通过事务完成。事务对象提供errorabortcomplete三个事件,用来监听操作结果。

三、操作流程

1、打开数据库:indexedDB.open()

var request = window.indexedDB.open(databaseName, version);

  这个方法接受两个参数,第一个参数是字符串,表示数据库的名字。如果指定的数据库不存在,就会新建数据库。第二个参数是整数,表示数据库的版本。如果省略,打开已有数据库时,默认为当前版本;新建数据库时,默认为1。

  indexedDB.open()方法返回一个 IDBRequest 对象。这个对象通过三种事件error、success、upgradeneeded,处理打开数据库的操作结果。

// error事件表示打开数据库失败
request.onerror = function (event) {
  console.log('数据库打开报错');
};

// success事件表示成功打开数据库
var db;
request.onsuccess = function (event) {
  db = request.result;
  console.log('数据库打开成功');
};

// upgradeneeded 事件
// 如果指定的版本号,大于数据库的实际版本号,就会发生数据库升级事件upgradeneeded
var db;
request.onupgradeneeded = function (event) {
  db = event.target.result;
}
// 这时通过事件对象的target.result属性,拿到数据库实例

2、新建数据库

  新建数据库与打开数据库是同一个操作。如果指定的数据库不存在,就会新建。不同之处在于,后续的操作主要在upgradeneeded事件的监听函数里面完成,因为这时版本从无到有,所以会触发这个事件。

  通常,新建数据库以后,第一件事是新建对象仓库(即新建表)。

request.onupgradeneeded = function(event) {
  db = event.target.result;
  var objectStore = db.createObjectStore('person', { keyPath: 'id' });
}
// 上面代码中,数据库新建成功以后,新增一张叫做person的表格,主键是id

  新建对象仓库以后,下一步可以新建索引

request.onupgradeneeded = function(event) {
  db = event.target.result;
  var objectStore = db.createObjectStore('person', { keyPath: 'id' });
  objectStore.createIndex('name', 'name', { unique: false });
  objectStore.createIndex('email', 'email', { unique: true });
}

  上面代码中,IDBObject.createIndex()的三个参数分别为索引名称、索引所在的属性、配置对象(说明该属性是否包含重复的值)

3、新增数据指的是向对象仓库写入数据记录。通过事务完成。

function add() {
  var request = db.transaction(['person'], 'readwrite')
    .objectStore('person')
    .add({ id: 1, name: '张三', age: 24, email: 'zhangsan@example.com' });
  request.onsuccess = function (event) {
    console.log('数据写入成功');
  };
  request.onerror = function (event) {
    console.log('数据写入失败');
  }
}
add();

  上面代码中,写入数据需要新建一个事务。新建时必须指定表格名称和操作模式(“只读”或“读写”)。新建事务以后,通过IDBTransaction.objectStore(name)方法,拿到 IDBObjectStore 对象,再通过表格对象的add()方法,向表格写入一条记录。

  写入操作是一个异步操作,通过监听连接对象的success事件和error事件,了解是否写入成功

4、读取数据也是通过事务完成

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('Name: ' + request.result.name);
        console.log('Age: ' + request.result.age);
        console.log('Email: ' + request.result.email);
      } else {
        console.log('未获得数据记录');
      }
   };
}
read();

5、遍历数据:遍历数据表格的所有记录,要使用指针对象 IDBCursor

function readAll() {
   var objectStore = db.transaction('person').objectStore('person');
   objectStore.openCursor().onsuccess = function (event) {
     var cursor = event.target.result;
     if (cursor) {
       console.log('Id: ' + cursor.key);
       console.log('Name: ' + cursor.value.name);
       console.log('Age: ' + cursor.value.age);
       console.log('Email: ' + cursor.value.email);
       cursor.continue();
    } else {
      console.log('没有更多数据了!');
    }
  };
}
readAll();

  上面代码中,新建指针对象的openCursor()方法是一个异步操作,所以要监听success事件。

6、更新数据要使用IDBObject.put()方法

function update() {
  var request = db.transaction(['person'], 'readwrite')
    .objectStore('person')
    .put({ id: 1, name: '李四', age: 35, email: 'lisi@example.com' });

  request.onsuccess = function (event) {
    console.log('数据更新成功');
  };
  request.onerror = function (event) {
    console.log('数据更新失败');
  }
}
update();

  上面代码中,put()方法自动更新了主键为1的记录。

7、删除数据:IDBObjectStore.delete()方法用于删除记录

function remove() {
  var request = db.transaction(['person'], 'readwrite')
    .objectStore('person')
    .delete(1);
  request.onsuccess = function (event) {
    console.log('数据删除成功');
  };
}
remove();

8、使用索引

  索引的意义在于,可以让你搜索任意字段,也就是说从任意字段拿到数据记录。如果不建立索引,默认只能搜索主键(即从主键取值)

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

objectStore.createIndex('name', 'name', { unique: false });

  现在,就可以从name找到对应的数据记录了

var transaction = db.transaction(['person'], 'readonly');
var store = transaction.objectStore('person');
var index = store.index('name');
var request = index.get('李四');
request.onsuccess = function (e) {
  var result = e.target.result;
  if (result) {
    // ...
  } else {
    // ...
  }
}

四、IndexedDB对象

indexedDB.open():打开数据库

  打开数据库是异步操作,通过各种事件通知客户端。下面是有可能触发的4种事件。

success:打开成功

error:打开失败

upgradeneeded:第一次打开该数据库,或者数据库版本发生变化

blocked:上一次的数据库连接还未关闭

  第一次打开数据库时,会先触发upgradeneeded事件,然后触发success事件。根据不同的需要,对上面4种事件监听函数。

indexedDB.deleteDatabase():删除数据库

  调用deleteDatabase()方法以后,当前数据库的其他已经打开的连接都会接收到versionchange事件

indexedDB.cmp():比较两个值是否为IndexedDB的相同的主键

  indexedDB.cmp()方法比较两个值是否为 indexedDB 的相同的主键。它返回一个整数,表示比较的结果:0表示相同,1表示第一个主键大于第二个主键,-1表示第一个主键小于第二个主键。

五、IDBRequest 对象:表示打开的数据库连接

  indexedDB.open()方法和indexedDB.deleteDatabase()方法会返回这个对象。数据库的操作都是通过这个对象完成的。

  这个对象的所有操作都是异步操作,要通过readyState属性判断是否完成,如果为pending就表示操作正在进行,如果为done就表示操作完成,可能成功也可能失败。

  操作完成以后,触发success事件或error事件,这时可以通过result属性和error属性拿到操作结果。如果在pending阶段,就去读取这两个属性,是会报错的。

Iquest.readyState:等于pending表示操作正在进行,等于done表示操作正在完成

IDBRequest.result:返回请求的结果。如果请求失败、结果不可用,读取该属性会报错

IDBRequest.error:请求失败时,返回错误对象

IDBRequest.source:返回请求的来源(比如索引对象或 ObjectStore)

IDBRequest.transaction:返回当前请求正在进行的事务,如果不包含事务,返回null

IDBRequest.onsuccess:指定success事件的监听函数

IDBRequest.onerror:指定error事件的监听函数

  IDBOpenDBRequest 对象继承了 IDBRequest 对象,提供了两个额外的事件监听属性。

IDBOpenDBRequest.onblocked:指定blocked事件(upgradeneeded事件触发时,数据库仍然在使用)的监听函数。
IDBOpenDBRequest.onupgradeneeded:upgradeneeded事件的监听函数

六、IDBDatabase对象:表示连接的数据库

  打开数据成功后,可以从IDBOpenDBRequest对象的result属性上面,拿到一个IDBDatabase对象。后面的数据库操作都是通过这个对象完成。

1、属性

IDBDatabase.name:string,数据库名称
IDBDatabase.version:int,数据库版本。数据库第一次创建时,该属性为空字符串
IDBDatabase.objectStoreNames:DOMStringList对象(字符串的集合),包含当前时间的所有object store的名字
IDBDatabase.onabort:指定abort事件(事务中止)的监听函数
IDBDatabase.onclose:指定close事件(数据库意外关闭)的监听函数
IDBDatabase.onerror:指定error事件(访问数据库失败)的监听函数
IDBDatabase.onversionchange:数据库版本发生变化时触发(发生upgradeneeded事件,或调用indexedDB.deleteDatabase())

2、方法:IDBDatabase对象有以下方法

IDBDatabase.close():关闭数据库连接,实际会等所有事务完成后再关闭
IDBDatabase.createObjectStore():创建存放数据的对象仓库,类似于传统关系型数据库的表格,返回一个IDBObjectStore对象,该方法只能在versionchange事件监听函数中调用
IDBDatabase.transaction():返回一个IDBTransaction事务对象

七、IDBObjectStore对象

  IDBObjectStore对象对应一个对象仓库。IDBDatabase.createObjectStore()方法返回的就是一个IDBObjectStore对象。

  IDBDatabase对象的transaction()返回一个事务对象,该方法的objectStore()方法返回一个IDBObjectStore对象

1、属性

IDBObjectStore.indexNames:返回一个类似数组的对象(DOMStringList),包含了当前对象仓库的所有索引
IDBObjectStore.keyPath:返回当前对象仓库的主键
IDBObjectStore.name:返回当前对象仓库的名称
IDBObjectStore.transaction:返回当前对象仓库所属的事务对象
IDBObjectStore.autoIncrement:布尔值,表示主键是否会自动递增

2、方法

(1)IDBObjectStore.add():用于向对象仓库添加数据,返回一个IDBRequest对象。该方法只用于添加数据,如果主键相同会报错,因此更新数据必须用put()方法。

  该方法接受两个参数,第一个参数是键值,第二个参数是主键,该参数可选,如果省略默认为null。

  创建事务以后,就可以获取对象仓库,然后使用add()方法往里面添加数据了。

(2)IDBObjectStore.put():用于更新某个主键对应的数据记录,如果对应的键值不存在,则插入一条新的记录。还方法返回一个IDBRequest对象。

  该方法接受两个参数,第一个参数为新数据,第二个参数为主键,该参数可选,且只在自动递增时才有必要提供,因为那时主键不包含在数据值里面。

(3)IDBObjectStore.clear():删除当前仓库对象的所有记录。该方法返回一个IDBRequest对象。

(4)IDBObjectStore.delete():用于删除指定主键的记录。该方法返回一个IDBRequest对象。

  该方法的参数为主键的值。

(5)IDBObjectStore.count():用于计算记录的数量。该方法返回一个IDBRequest对象。

  不带参数时,该方法返回当前对象仓库的所有记录数量。如果主键或 IDBKeyRange 对象作为参数,则返回对应的记录数量。

(6)IDBObjectStore.getKey():用于获取主键。该方法返回一个 IDBRequest 对象。

  该方法的参数可以是主键值或 IDBKeyRange 对象。

(7)IDBObjectStore.get():用于获取主键对应的数据记录。该方法返回一个 IDBRequest 对象。

  get()是异步方法,读取成功以后,只能在success事件的监听函数中处理数据。

(8)IDBObjectStore.getAll():用于获取对象仓库的记录。该方法返回一个 IDBRequest 对象。

(9)IDBObjectStore.getAllKeys():用于获取所有符合条件的主键。该方法返回一个 IDBRequest 对象。

(10)IDBObjectStore.index():返回指定名称的索引对象 IDBIndex。有了索引以后,就可以针对索引所在的属性读取数据

(11)IDBObjectStore.createIndex():用于新建当前数据库的一个索引。该方法只能在VersionChange监听函数里面调用。

objectStore.createIndex(indexName, keyPath, objectParameters)

  该方法可以接受三个参数:

  indexName:索引名

  keyPath:主键

  objectParameters:配置对象(可选)

  第三个参数可以配置以下属性:

  unique:如果设为true,将不允许重复的值

  multiEntry:如果设为true,对于有多个值的主键数组,每个值将在索引里面新建一个条目,否则主键数组对应一个条目

(12)IDBObjectStore.openCursor():用于获取一个指针对象。指针对象可以用来遍历数据。该对象也是异步的,有自己的successerror事件,可以对它们指定监听函数

var t = db.transaction(['test'], 'readonly');
var store = t.objectStore('test');
var cursor = store.openCursor();
cursor.onsuccess = function (event) {
  var res = event.target.result;
  if (res) {
    console.log('Key', res.key);
    console.dir('Data', res.value);
    res.continue();
  }
}

  监听函数接受一个事件对象作为参数,该对象的target.result属性指向当前数据记录。该记录的key和value分别返回主键和键值(即实际存入的数据)。

  continue()方法将光标移到下一个数据对象,如果当前数据对象已经是最后一个数据了,则光标指向null。

  openCursor()方法的第一个参数是主键值,或者一个 IDBKeyRange 对象。如果指定该参数,将只处理包含指定主键的记录;如果省略,将处理所有的记录。该方法还可以接受第二个参数,表示遍历方向,默认值为next,其他可能的值为prev、nextunique和prevunique。后两个值表示如果遇到重复值,会自动跳过。

(13)IDBObjectStore.openKeyCursor():用于获取一个主键指针对象

八、IDBTransaction 对象

  IDBTransaction 对象用来异步操作数据库事务,所有的读写操作都要通过这个对象进行。IDBDatabase.transaction()方法返回的就是一个 IDBTransaction 对象。

  事务的执行顺序是按照创建的顺序,而不是发出请求的顺序。事务有可能失败,只有监听到事务的complete事件,才能保证事务操作成功。

1、属性

IDBTransaction.db:返回当前事务所在的数据库对象IDBDatabase
IDBTransaction.error:返回当前事务的错误。如果事务没有结束,或者事务成功结束,或者被手动终止,该方法返回null
IDBTransaction.mode:返回当前事务的模式,默认是readonly,另一个值是readwrite
IDBTransaction.objectStoreNames:返回一个类似数组的对象DOMStringList,成员是当前事务涉及的对象仓库的名字
IDBTransaction.onabort:指定abort事件(事务中断)的监听函数
IDBTransaction.oncomplete:指定complete事件(事务成功)的监听函数
IDBTransaction.onerror:指定error事件(事务失败)的监听事件

2、方法

IDBTransaction.abort():终止当前事务,回滚所有已经进行的变更
IDBTransaction.objectStore(name):返回指定名称的对象仓库IDBObjectStore

九、IDBIndex对象

  IDBIndex对象代表数据库的索引,通过这个对象可以获取数据库里面的记录。数据记录的主键默认就是带有索引,IDBIndex对象主要用于通过除主键外的其他键,建立索引获取对象。

  IDBIndex是持久性的键值对存储。只要插入、更新或删除数据记录,引用的对象库中的记录,索引就会自动更新。

  IDBObjectStore.index()方法可以获取 IDBIndex 对象。

1、属性

IDBIndex.name:string,索引的名称
IDBIndex.objectStore:索引所在的对象仓库
IDBIndex.keyPath:索引的主键
IDBIndex.multiEntry:Boolean,表示创建索引时是否允许相同的主键

2、方法:都是异步方法,立即返回的都是一个IDBRequest对象

IDBIndex.count()用来获取记录的数量。可以直接接受主键或IDBKeyRange对象作为参赛,这时只返回符合主键的记录数量,否则返回所有记录的数量
IDBIndex.get(key)用来获取符合指定主键的数据记录
IDBIndex.getKey(key)用来获取指定的主键
IDBIndex.getAll()用来获取所有的数据记录。可以接受两个参数,都是可选的,第一个参数用来指定主键,第二个参数用来指定返回记录的数量。
如果省略这两个参数,则返回所有记录。由于获取成功时,浏览器必须生成所有对象,所以对性能有影响。如果数据集比较大,建议使用IDBCursor对象。 IDBIndex.getAllKeys()与IDBIndex.getAll()相似,区别是获取所有主键 IDBIndex.openCursor()用来获取一个IDBCursor对象,用来遍历索引里面的所有条目 IDBIndex.openKeyCursor()与IDBIndex.openCursor()相似,区别是遍历所有条目的主键

十、IDBCursor对象

  IDBCursor 对象代表指针对象,用来遍历数据仓库(IDBObjectStore)或索引(IDBIndex)的记录。

  IDBCursor 对象一般通过IDBObjectStore.openCursor()方法获得

1、属性

IDBCursor.source:返回正在遍历的对象仓库或索引
IDBCursor.direction:string,表示指针遍历的方向
共有四个可能的值:
    next(从头开始向后遍历)、
    nextunique(从头开始向后遍历,重复的值只遍历一次)、
    prev(从尾部开始向前遍历)、
    prevunique(从尾部开始向前遍历,重复的值只遍历一次)。
该属性通过IDBObjectStore.openCursor()方法的第二个参数指定,一旦指定就不能改变了
IDBCursor.key:返回当前记录的主键
IDBCursor.value:返回当前记录的数据值
IDBCursor.primaryKey:返回当前记录的主键
对于数据仓库(ObjectStore)来说,这个属性等同于IDBCursor.key;对于索引,IDBCursor.key返回索引的位置值,该属性返回数据记录的主键

2、方法

IDBCursor.advance(n)指针向前移动n个位置
IDBCursor.continue()指针向前移动一个位置。可以接受一个主键作为参数,这时会跳转到这个主键。
IDBCursor.continuePrimaryKey()需要两个参数,第一个是key,第二个是primaryKey,将指针移到符合这两个参数的位置。
IDBCursor.delete()用来删除当前位置的记录,返回一个IDBRequest对象。该方法不会改变指针的位置。
IDBCursor.update()用来更新当前位置的记录,返回一个IDBRequest对象。参数是要写入数据库的新的值

十一、IDBKeyRange对象

  IDBKeyRange 对象代表数据仓库(object store)里面的一组主键。根据这组主键,可以获取数据仓库或索引里面的一组记录。

  IDBKeyRange 可以只包含一个值,也可以指定上限和下限。它有四个静态方法,用来指定主键的范围

    IDBKeyRange.lowerBound():指定下限
    IDBKeyRange.upperBound():指定上限
    IDBKeyRange.bound():同时指定上下限
    IDBKeyRange.only():指定只包含一个值

  IDBKeyRange.lowerBound()IDBKeyRange.upperBound()IDBKeyRange.bound()这三个方法默认包括端点值,可以传入一个布尔值,修改这个属性

1、属性:都为只读属性

IDBKeyRange.lower:返回下限
IDBKeyRange.lowerOpen:Boolean,表示下限是否为开区间
IDBKeyRange.upper:返回上限
IDBKeyRange.upperOpen:Boolean,表示上限是否为开区间

2、方法

IDBKeyRange.includes(key):返回一个布尔值,表示某个主键是否包含在当前这个主键组之内

 

posted @ 2023-12-17 14:43  古兰精  阅读(923)  评论(0编辑  收藏  举报