IndexedDB小记

60 阅读6分钟

现有的浏览器端数据储存方案,都不适合储存大量数据:cookie不超过4KB,且每次请求都会发送回服务器端;localStorage在2.5MB到10MB之间(各家浏览器不同)。所以,需要一种新的解决方案,这就是IndexedDB诞生的背景。

通俗地说,IndexedDB就是浏览器端数据库,可以被网页脚本程序创建和操作。它允许储存大量数据,提供查找接口,还能建立索引。这些都是localStorage所不具备的。就数据库类型而言,IndexedDB不属于关系型数据库(不支持SQL查询语句),更接近NoSQL数据库。

IndexedDB具有以下特点。

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

下面的代码用来检查浏览器是否支持这个API。

if("indexedDB" in window) {
    // 支持
} else {
    // 不支持
}

indexedDB.open方法


/*
 *@databaseName 数据仓库的名字
 *@version 数据仓库的版本 (大于0的正整数)
 */
var openRequest = indexedDB.open("test",1);

第一次打开数据库时,会先触发upgradeneeded事件,然后触发success事件。

var openRequest = indexedDB.open("test",1);
var db;

/*
 *数据仓库升级事件
 */
openRequest.onupgradeneeded = function(e) {
    console.log('IndexedDB 升级成功', e);
    db = e.target.result;
}
 
/*
 *数据仓库打开成功
 */
openRequest.onsuccess = function(e) {
    console.log("Success!");
    db = e.target.result;
}
 
/*
 *数据仓库打开失败
 */
openRequest.onerror = function(e) {
    console.log("Error");
    console.dir(e);
}

关闭和删除数据库

db.close();
window.indexedDB.deleteDatabase(dbName);

createObjectStore

createObjectStore方法用于创建存放数据的“对象仓库”

db.createObjectStore("group");

如果该对象仓库已经存在,就会抛出一个错误。为了避免出错,需要用到下文的objectStoreNames属性,检查已有哪些对象仓库。

db.createObjectStore("group", { keyPath: "email" }); 
db.createObjectStore("group", { autoIncrement: true });

上面代码中的keyPath属性表示,所存入对象的email属性用作每条记录的键名(由于键名不能重复,所以存入之前必须保证数据的email属性值都是不一样的),默认值为null;autoIncrement属性表示,是否使用自动递增的整数作为键名(第一个数据为1,第二个数据为2,以此类推),默认为false。一般来说,keyPath和autoIncrement属性只要使用一个就够了,如果两个同时使用,表示键名为递增的整数,且对象不得缺少指定属性。

objectStoreNames

if(!db.objectStoreNames.contains("group")) {
     db.createObjectStore("group");
}

transaction

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

transaction方法接受两个参数:第一个参数是一个数组,里面是所涉及的对象仓库,通常是只有一个;第二个参数是一个表示操作类型的字符串。目前,操作类型只有两种:readonly(只读)和readwrite(读写)。添加数据使用readwrite,读取数据使用readonly。

transaction方法返回一个事务对象,该对象的objectStore方法用于获取指定的对象仓库。

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

var store = t.objectStore("group");

事务对象有以下方法,用于操作数据。

(1)添加数据:add方法

var store = t.objectStore("group");

var o = {p: 123};

var request = store.add(o,1);

add方法的第一个参数是所要添加的数据,第二个参数是这条数据对应的键名(key),上面代码将对象o的键名设为1。如果在创建数据仓库时,对键名做了设置,这里也可以不指定键名。

add方法是异步的,有自己的success和error事件,可以对这两个事件指定回调函数。

var request = store.add(o,1);

request.onerror = function(e) {
     console.log("Error",e.target.error.name);
    // error handler
}

request.onsuccess = function(e) {
    console.log("数据添加成功!");
}

(2)读取数据:get方法

参数是数据的键名

var t = db.transaction(["group"], "readonly");
var store = t.objectStore("group");

var ob = store.get(x);

(3)更新记录:put方法

var o = { p:456 };
var request = store.put(o, 1);

(4)删除记录:delete方法

delete方法的参数是数据的键名。

var t = db.transaction(["group"], "readwrite");
var request = t.objectStore("group").delete(thisId);

createIndex

createIndex方法用于创建索引。

可以指定这个数据对象的某个属性来建立索引。

var store = db.createObjectStore("group", { autoIncrement:true });

store.createIndex("name","name", {unique:false});
store.createIndex("email","email", {unique:true});

createIndex方法接受三个参数,第一个是索引名称,第二个是建立索引的属性名,第三个是参数对象,用来设置索引特性。unique表示索引所在的属性是否有唯一值,上面代码表示name属性不是唯一值,email属性是唯一值。

对已存在的表创建索引时, 需要先升级版本

var openRequest = indexedDB.open("test",2);
var db;

/*
 *数据仓库升级事件
 */
openRequest.onupgradeneeded = function(e) {
    console.log('IndexedDB 升级成功', e);
    const store = e.target.transaction.objectStore('group')
    store.createIndex("name","name", {unique:false});
}
 

index方法

有了索引以后,就可以针对索引所在的属性读取数据。index方法用于从对象仓库返回指定的索引。

var t = db.transaction(["group"],"readonly");
var store = t.objectStore("group");
var index = store.index("name");

var request = index.get(name);

IDBKeyRange

  • lowerBound方法:指定范围的下限。
  • upperBound方法:指定范围的上限。
  • bound方法:指定范围的上下限。
  • only方法:指定范围中只有一个值。
// All keys ≤ x	
var r1 = IDBKeyRange.upperBound(x);

// All keys < x	
var r2 = IDBKeyRange.upperBound(x, true);

// All keys ≥ y	
var r3 = IDBKeyRange.lowerBound(y);

// All keys > y	
var r4 = IDBKeyRange.lowerBound(y, true);

// All keys ≥ x && ≤ y	
var r5 = IDBKeyRange.bound(x, y);

// All keys > x &&< y	
var r6 = IDBKeyRange.bound(x, y, true, true);

// All keys > x && ≤ y	
var r7 = IDBKeyRange.bound(x, y, true, false);

// All keys ≥ x &&< y	
var r8 = IDBKeyRange.bound(x, y, false, true);

// The key = z	
var r9 = IDBKeyRange.only(z);
var store = db.transaction(['group']).objectStore('group');
// 获取id名称小于当前时间的所有data
var request = store.getAll(IDBKeyRange.upperBound(+new Date()));

/*
 *更新成功
 */
request.onsuccess = function(event) {
  console.log('indexedDB getAll:', event.target.result);
};

/*
 *更新失败
 */
request.onerror = function(event) {
  console.log('indexedDB getAll:', event);
};

生成Range对象以后,将它作为参数输入openCursor方法,就可以在所设定的范围内读取数据。

var t = db.transaction(["group"],"readonly");
var store = t.objectStore("group");
var index = store.index("name");

var range = IDBKeyRange.only(123);

index.openCursor(range).onsuccess = function(e) {
        var cursor = e.target.result;
        if(cursor) {
            console.log(cursor.key) // 当前遍历数据的主键
            console.log(cursor.value) // 当前遍历的数据
            cursor.continue() // 继续下一个
        }
}