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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容