Progressive Web Apps(PWA)核心技术-Indexed DB

在使用的过程中我们通常使用cache缓存html、css、js等文件信息,但是一些特殊的数据需要我们借助数据库的支持,这里推荐使用IndexedDB。

IndexedDB是一个大型的noSQL存储系统。 它使您可以在用户的浏览器中存储任何内容。 除了通常的搜索,获取和存储操作之外,IndexedDB还支持事务。

检查浏览器是否支持IndexedDB

if (!('indexedDB' in window)) {
  console.log('This browser doesn\'t support IndexedDB');
  return;
}

创建数据库
使用IndexedDB我们可以创建多个数据库,但是原则上我们每个应用程序只需要建立一个数据库。

name:数据库名称,version:版本号,upgradeCallback:回调方法

idb.open(name, version, upgradeCallback)

IndexedDB之对象存储

主要通过创建数据库是回调返回的实例调用createObjectStore方法来创建对象存储。

 if (!('indexedDB' in window)) {
    console.log('This browser doesn\'t support IndexedDB');
    return;
  }

  var dbPromise = idb.open('test-db2', 1, function(upgradeDb) {
    console.log('making a new object store');
    if (!upgradeDb.objectStoreNames.contains('firstOS')) {
      upgradeDb.createObjectStore('firstOS');
    }
  });
  • 定义主键并设置自增
    使用keyPath定义主键,使用autoIncrement设置主键自增。
  var dbPromise = idb.open('test-db3', 1, function(upgradeDb) {

    if (!upgradeDb.objectStoreNames.contains('logs')) {
      upgradeDb.createObjectStore('logs', {keyPath: 'id', autoIncrement: true});
    }
  });
  • 定义索引
    创建索引,在对象存储实例上调用createIndex方法:
objectStore.createIndex('indexName','property',options);

这个方法创建并返回一个索引对象。 createIndex将新索引的名称作为第一个参数,第二个参数指向要索引的数据的属性。最后一个参数可以让你定义两个选项来决定索引如何工作:unique和multiEntry。如果unique设置为true,则索引不允许单个键的重复值。当索引属性是一个数组时,multiEntry决定了createIndex的行为。如果设置为true,则createIndex将在每个数组元素的索引中添加一个条目。否则,它会添加一个包含该数组的条目。

  var dbPromise = idb.open('test-db4', 1, function(upgradeDb) {
    if (!upgradeDb.objectStoreNames.contains('people')) {
      var peopleOS = upgradeDb.createObjectStore('people', {keyPath: 'email'});
      peopleOS.createIndex('gender', 'gender', {unique: false});
      peopleOS.createIndex('ssn', 'ssn', {unique: true});
    }
  });

注意:每次将数据写入参考对象库时,索引都会更新。 索引越多意味着IndexedDB的工作量越大。

IndexedDB之操作数据
IndexedDB中的所有数据操作都是在一个事务中执行的。 每个操作都有这样的形式:

1.获取数据库对象
2.在数据库上打开事务
3.在事务上打开对象存储
4.在对象存储上执行操作

写入数据
要创建数据,请在对象存储上调用add方法,并传入要添加的数据。 Add有一个可选的第二个参数,它可以让你在创建时为单个对象定义主键,但是只有当你没有在createObjectStore中指定关键路径时才能使用它。

someObjectStore.add(data, optionalKey);

dbPromise.then(function(db) {
  var tx = db.transaction('store', 'readwrite');//开启读写事物
  var store = tx.objectStore('store');
  var item = {
    name: 'sandwich',
    price: 4.99,
    description: 'A very tasty sandwich',
    created: new Date().getTime()
  };
  store.add(item);
  return tx.complete;
}).then(function() {
  console.log('added item to the store os!');
});

读取数据
要读取数据,请调用对象存储上的get方法。 get方法使用要从存储中检索的对象的主键。

someObjectStore.get(primaryKey);

dbPromise.then(function(db) {
  var tx = db.transaction('store', 'readonly');//开启只读事物
  var store = tx.objectStore('store');
  return store.get('sandwich');//如果没有该值则返回undefined
}).then(function(val) {
  console.dir(val);
});

更新数据
要更新数据,请在对象存储上调用put方法。 put方法与add方法非常相似,可以用来代替add来在对象存储中创建数据。 像add一样,put一个数据和一个可选的主键:

someObjectStore.put(data, optionalKey);

dbPromise.then(function(db) {
  var tx = db.transaction('store', 'readwrite');
  var store = tx.objectStore('store');
  var item = {
    name: 'sandwich',
    price: 99.99,
    description: 'A very tasty, but quite expensive, sandwich',
    created: new Date().getTime()
  };
  store.put(item);
  return tx.complete;
}).then(function() {
  console.log('item updated!');
});

删除数据
要删除数据,在存储对象上调用delete方法。

someObjectStore.delete(primaryKey);

dbPromise.then(function(db) {
  var tx = db.transaction('store', 'readwrite');
  var store = tx.objectStore('store');
  store.delete(key);
  return tx.complete;
}).then(function() {
  console.log('Item deleted');
});

获取所有数据

我们还可以使用getAll方法或使用游标从对象存储或索引中检索所有数据(或子集)。

  • 使用getAll方法
    此方法返回与指定的键或键范围匹配的对象存储中的所有对象。

someObjectStore.getAll(optionalConstraint);

dbPromise.then(function(db) {
  var tx = db.transaction('store', 'readonly');
  var store = tx.objectStore('store');
  return store.getAll();
}).then(function(items) {
  console.log('Items by name:', items);
});
  • 使用游标
    另一种检索所有数据的方法是使用游标。 游标会逐个选择对象存储或索引中的每个对象,让您在选择数据时对其执行操作。

someObjectStore.openCursor(optionalKeyRange, optionalDirection);

dbPromise.then(function(db) {
  var tx = db.transaction('store', 'readonly');
  var store = tx.objectStore('store');
  return store.openCursor();
}).then(function logItems(cursor) {
  if (!cursor) {
    return;
  }
  console.log('Cursored at:', cursor.key);
  for (var field in cursor.value) {
    console.log(cursor.value[field]);
  }
  return cursor.continue().then(logItems);
}).then(function() {
  console.log('Done cursoring');
});

这个方法返回一个promise,用游标对象表示对象存储中的第一个对象,如果没有对象,则返回undefined。 要移动到对象存储中的下一个对象,我们调用cursor.continue。

我们首先获取数据库对象,创建一个事务,并打开一个对象存储。我们在对象存储上调用openCursor方法,并将游标对象传递给.then中的回调函数。这次我们将回调函数命名为“logItems”,所以我们可以从函数内部调用它并进行循环。 if(!cursor){return;}如果store.openCursor()返回的promise被解析为undefined,或者cursor.continue()返回的promise被解析为undefined(表示没有更多的对象)。

游标对象包含一个代表项目主键的键属性。它还包含一个代表数据的值属性。在logItems结束时,我们返回cursor.continue().then(logItems)。 cursor.continue方法返回一个promise,该promise解析为表示store中下一个store的游标对象,如果没有更多的对象,则返回undefined。这个结果被传递到.then中的回调函数中,我们选择把logItems作为这个函数,这样函数就会循环。因此,logItems继续调用自己,直到没有对象保留。

通过索引获取某个范围内数据

我们可以通过不同的方式获得所有的数据,但是如果我们只需要基于某个特定属性的数据子集呢? 这时我们可以使用索引。索引让我们通过主键以外的属性获取对象存储中的数据。 我们可以在任何属性上创建索引,在该属性上指定范围,并使用getAll方法或游标获取范围内的数据。

我们使用IDBKeyRange对象来定义范围。 这个对象有四个方法来定义范围的限制:upperBound,lowerBound,bound(这意味着两者),only。 upperBound和lowerBound方法指定范围的上限和下限。

IDBKeyRange.lowerBound(indexKey); IDBKeyRange.upperBound(indexKey); IDBKeyRange.bound(lowerIndexKey, upperIndexKey); IDBKeyRange.only(indexKey);

function searchItems(lower, upper) {
  if (lower === '' && upper === '') {return;}

  var range;
  if (lower !== '' && upper !== '') {
    range = IDBKeyRange.bound(lower, upper);
  } else if (lower === '') {
    range = IDBKeyRange.upperBound(upper);
  } else {
    range = IDBKeyRange.lowerBound(lower);
  }

  dbPromise.then(function(db) {
    var tx = db.transaction(['store'], 'readonly');
    var store = tx.objectStore('store');
    var index = store.index('price');//打开索引
    return index.openCursor(range);
  }).then(function showRange(cursor) {
    if (!cursor) {return;}
    console.log('Cursored at:', cursor.key);
    for (var field in cursor.value) {
      console.log(cursor.value[field]);
    }
    return cursor.continue().then(showRange);
  }).then(function() {
    console.log('Done cursoring');
  });
}

使用数据库版本号

var dbPromise = idb.open('test-db7', 3, function(upgradeDb) {
  switch (upgradeDb.oldVersion) {
    case 0:
      upgradeDb.createObjectStore('store', {keyPath: 'name'});
    case 1:
      var storeOS = upgradeDb.transaction.objectStore('store');
      storeOS.createIndex('price', 'price');
    case 2:
      var storeOS = upgradeDb.transaction.objectStore('store');
      storeOS.createIndex('description', 'description');
  }
});

如果浏览器中没有该数据库,那么oldVersion将为0,从case:0开始,依次执行0、1、2,创建主键为name的存储对象,然后为该对象创建一个price的索引和一个description的索引。

如果浏览器中存在该数据库,并且已经创建了一个名为price的索引,那么它的oldVersion为2,则0,1将被跳过,直接创建一个名为description的索引。

注意:这里的case没有break;

参考链接:
https://developers.google.com/web/ilt/pwa/working-with-indexeddb#getting_all_the_data

你可能感兴趣的:(Progressive Web Apps(PWA)核心技术-Indexed DB)