在 浏览器数据库 IndexedDB 简明教程 ( 四 ) - 创建与打开数据库 章节中我们学习了如何创建和打开 IndexedDB
数据库,学习了 IDBOpenDBRequest
对象的三个时间属性,也分别对它们做了一些介绍,我们主要当 IndexedDB
把数据库成功创建了一个新的版本时会触发 onupgradeneeded
事件
数据库版本升级机制
也许是上一个章节我们没有表述清楚,我们并没有明确表达 onupgradeneeded
事件的触发时机,在这里,我们细细阐述一下
「 当使用 open
方法打开一个 IndexedDB
数据库时,如果发现传递了一个新的版本,则 IndexedDB
,会自动将数据库版本编号改成新的,如果成功,则再回调 onupgradeneeded
事件 」
也就是说,对于下面的 JavaScript 代码
req.onupgradeneeded = function (event) { console.log(event.target.result); console.log('升级成功'); }
event.target.result
所代表的数据库,里面的数据还是原来的,只不过只更改了数据库的版本编号而已,真正的数据库升级,是通过调用 onupgradeneeded
来完成,如果 onupgradeneeded
没有创建任何存储对象或主键或索引,那么,数据库还是原来那个数据库,只不过编号不一样了而已
所以,当我们需要将数据库升级时,一般会做写如下的代码
req.onupgradeneeded = function(event) { db = event.target.result; var cityStore = db.createObjectStore('city', { keyPath: 'city_id' }); }
先不用管 createObjectStore
是啥意思,上面这个回调的意思是,如果版本提升成功,那么继续创建一个存储对象 city
,并且指定主键为 city_id
当然了,一般情况下我们不会这么写,因为我们需要根据当前的版本号来指定如何升级,所以,正常的写法如下
req.onupgradeneeded = function(event) { db = event.target.result; // 特别指定,只要版本号为 3 时才会这么多 if ( db.version == 3 ) var schoolObject = db.createObjectStore('city', { keyPath: 'school_id' }) }
但是,这个上面这个版本还是不能再正式环境中使用的,因为我们并不知道之前的版本号是什么?如果只升级到最新的版本,那么中间的版本就可能遗漏了,所以,能用在正式环境的完整代码如下
// 先定义当前最新的版本,一般从服务器端获取 const latest_version = 3; // 定义最新版本的数据库 let db; // 定义一个变量,用于提示是否需要升级数据库版本号 var need_update_version = -1; //尝试打开当前版本的数据库,如果没创建则创建一个 var req = window.indexedDB.open('demo'); req.onerror = function (event) { console.log('打开数据库失败'); }; req.onupgradeneeded = function(event){ let old_version = event.target.result.version; // 检查数据库版本是否最新,如果最新则直接返回 if ( old_version == latest_version ) db = event.target.result; // 如果数据库版本小于 2 则执行一些更新 if ( old_version < 2 ) { need_update_version = 2; var schoolObject = db.createObjectStore('city', { keyPath: 'school_id' }); } // 如果数据库版本小于 3 则执行另一些更新 if ( old_version < 3 ) { need_update_version = 3; var addressObject = db.createObjectStore('address', { keyPath: 'address_id' }); } console.log('打开数据库成功'); }; req.onsuccess = function (event) { // 如果检测到需要更新数据库版本,什么事情都不做,否则设置 db 对象 if ( need_update_version == -1 ) db = event.target.result; } // 一个坑爹的死轮询开始了, 50ms 轮询一次 const tick = setInterval(function(){ // 如果 db 有值,则说明没发生升级,直接清楚定时器 if ( db ) { clearInterval(tick); } else if ( need_update_version > 0 && need_update_version != latest_version ) { // 否则判断升级后的版本是否是最新的,如果不是最新的,说明升级失败,提示即可,也退出 alert('升级 IndexedDB 失败'); clearInterval(tick); } else if ( need_update_version == latest_version ) { // 升级成功,用最新的版本打开数据库 //尝试打开最新版本的数据库,如果没创建则创建一个 var req = window.indexedDB.open('demo',latest_version); req.onerror = function (event) { console.log('打开数据库失败'); }; // req.onupgradeneeded 事件已经没啥用了 // 打开成功 req.onsuccess = function (event) { db = event.target.result; } } else { // 说明升级还没完成,继续等待 } },50);
一个版本升级,都这么坑爹,所以,大部分人为了方便起见,直接强行打开最新的版本,然后不管三七二十一,把最新版本所需要的存储对象啦,主键啦索引啦,一股脑儿的全部创建了
创建一个新的数据库也会触发 onupgradeneeded
让我们回到上一章节的第一次执行以下代码的结果
let db; const req = window.indexedDB.open('demo'); req.onerror = function (event) { console.log('打开数据库失败'); }; req.onsuccess = function (event) { db = event.target.result; console.log('打开数据库成功'); }; req.onupgradeneeded = function (event) { console.log(event.target); console.log('升级成功'); }
运行结果如下
升级成功 打开数据库成功
你会发现一个惊人的事情,就是首次运行竟然会出现 升级成功
,这是一个非常有趣的特性,因为有了这个特性,我们上面的介绍的 「 主键啦索引啦,一股脑儿的全部创建了 」 就有了新的解决方案
创建一个数据库的新版本并执行更新
因为无论是数据库为创建,或者需要升级,IndexedDB
都会触发 onupgradeneeded
事件,所以我们有了一个大胆的方案,那就是
「 无论如何都创建数据库的最新版本,然后再 onupgradeneeded
回调中根据有无需要的更新结果选择是否重新更新 」
例如下面的代码,如果没有检测到 city
这个存储对象,那么就新建一个 city
存储对象
req.onupgradeneeded = function (event) { db = event.target.result; var objectStore; if (!db.objectStoreNames.contains('city')) { objectStore = db.createObjectStore('city', { keyPath: 'city_id' }); } }
这样一来,就简单的多了,真的是要扶额擦汗,还好有变通的方法
结束语
在数据库版本升级的过程中,我们差点误入歧途,还好中间我们迷途知返,找了一个简单的解决方案