阅读 146

TagDown 扩展程序开发——前端数据持久化之 IndexedDB 基础

TagDown 是一款开源的书签管理插件, 您可以使用扩展程序浏览新增修改书签,它也支持以不同方式导出书签。

除了常见的书签管理功能,还具有以下特点:

  • 支持 ???? 新增书签,并附加额外的信息,例如 tagsgroups

  • 支持 ???? 导出任意书签为 json 文档

  • 以 ???? 树图的形式浏览层级结构的书签数据

  • 一键打开多个书签,支持在 ???? 标签组内打开书签


参考:

  • 《现代 JavaScript 教程》IndexedDB 一章

  • A closer look at IndexedDB

前端数据持久化有多种方式实现,其中较常用的是通过浏览器自带的 IndexedDB 数据库实现。

???? 由于扩展程序的后台 service worker 由于 Service workers 无法访问 DOM 和相关的 API,且环境中没有 window 这个变量对象,所以无法使用浏览器提供的 localStoragesessionStorage 进行数据存储。Chrome 为扩展程序提供一个数据存储 API chrome.storeage ,该 API 可以实现 类似 localStorage 的功能,用于存储少量的数据(一般是存储扩展程序的设置参数)。对于 sync 同步存储的数据,允许总大小为 100KB;对于 local 本地存储的数据,允许总大小为 5MB(类似于 localstorage 的存储限制)。

IndexedDB 具有以下特点:

  • 通过支持多种类型的键,来存储几乎可以是任何类型的值

  • 支撑事务,有良好的可靠性(事务是数据库通用术语,是指一组操作要么全部成功,要么全部失败,不存在中间结果从而导致数据冲突或不完整)

  • 支持键的范围查询,也支持为数据添加索引

⚠️ IndexedDB 遵循同源策略限制,即每个数据库都是绑定到源(域/协议/端口)的,不同的网站不能相互访问对方的数据库。

基本概念

IndexedDB 数据有几个基本的概念,和 SQL、NoSQL 等常用数据库概念类似:

  • (同源)网页可以创建一个专属的数据库 Database(遵循同源策略的限制)

  • 每一个数据库可以创建多个对象库 ObjectStore,和 SQL 的表格概念类似

  • 在对象库中就是以键值对的形式存储着的一条条数据

IndexedDB.png

使用 IndexedDB 的基本流程如下:

  • 创建/打开一个数据库

  • 定义数据结构,创建对象库,指定数据项的键 key(还可以创建索引 index)

  • 启动事务对对数据进行操作

  • 监听相应的事件

  • 获取操作结果

数据库

使用方法 open()(连接)一个数据库,第一个参数 name 是数据库名称,数据库可以有许多不同的名称;第二个参数是一个正整数,表示数据库版本,默认为 1

let openRequest = indexedDB.open(name, version);  复制代码

该方法返回 openRequest 对象,我们需要监听该对象上的事件(因为对 IndexedDB APIs 一般都是异步操作,待事件触发后才可以在回调函数中执行后续操作):

  • success:数据库准备就绪时触发的事件。然后在 openRequest.result 中有了一个数据库对象 Database Object,使用它对数据库进行进一步的调用

  • error:打开失败时触发的事件

  • upgradeneeded:数据库已准备就绪,但其版本已过时触发的事件。可以根据需要比较版本,并升级数据结构。升级操作顺利完成后,onsuccess 事件被触发,数据库才算是成功打开了

???? 如果数据库还不存在时(从此时数据库的版本是 0),打开数据库操作就会触发 upgradeneeded 事件,此时可以执行初始化(如创建对象库,指定数据项的键 key,定义数据项的索引 index 等)

⚠️ 只有在 upgradeneeded 事件的回调函数中,才可以更新升级数据库的结构(例如对象库的创建,以及修改对象库的索引属性)

let openRequest = indexedDB.open("tagdown", 1); // 如果浏览器中没有该数据库(或已存在的数据库版本,与打开的版本不符),则会触发 upgradeneeded 事件 openRequest.onupgradeneeded = function() {   // 执行初始化   let bookmark = db.createObjectStore('bookmark', { keyPath: "id" });   let index = bookmark.createIndex('tags', 'tags', { multiEntry: true}) // 参数依此表示:索引名称为 tags,对应以数据的哪一个属性作为索引值,由于该属性值为数组,且将数组的每个元素作为索引时需要配置 multiEntry 为 true }; // 如果浏览器中有该数据库,则会触发 onsuccess 事件 openRequest.onsuccess = function() {   let db = openRequest.result;   // 继续使用已有的 db 对象处理数据库 }; openRequest.onerror = function() {   console.error("Error", openRequest.error); }; 复制代码

???? 如果想删除整个数据库(而不是单一条数据),可以使用方法 deleteDatabase(databaseName)

事务

接着,success 事件的回调函数中,使用方法 transaction 启动事务,对数据进行操作。

使用事务操作数据的基本流程如下:

  • 使用 db.transaction(store) 创建一个事务,表明要访问的对象库

  • 使用 transaction.objectStore(name) 获取存储对象

  • 发起请求,操作数据

  • 监听请求的成功/错误事件,并执行相应的操作

方法 transaction 创建一个事务,它接收的第一个参数是事务要访问的对象库名称(如果我们要访问多个对象库,则该参数值是抑恶数组);(可选)第二个参数是事务类型:

  • readonly 只读,默认值

  • readwrite 可读取和写入数据(但不能创建/删除/更改的对象库,这类操作只能在 upgradeneeded 事件的回调函数中进行)

db.transaction(store[, type]); 复制代码

???? 需要两种事务类型,是因为两种类型的事物操作「性能」是不同的。许多 readonly 事务能够同时访问同一存储区;但 readwrite 事务不能。因为 readwrite 事务会「锁定」存储区进行写操作,下一个事务必须等待前一个事务完成,才能访问相同的存储区。

创建事务后,使用事务实例的方法 objectStore(name) 获取相应的对象库,然后就可以对里面的数据项进行操作。对象库支持两种存储值的方法:

  • 方法 put(value, [key])value 添加到存储区。如果已经存在相同键的数据,则将替换该值。

  • 方法 add(value, [key]) 与方法 put 类似,但是如果已经存在相同键的数据,则请求失败,并生成一个名为 "ConstraInterror" 的错误。

let transaction = db.transaction("bookmark", "readwrite"); // 创建一个事务,表明要访问的对象库是 bookmark,事务类型是读写 // 获取对象库进行操作 let bookmark = transaction.objectStore("bookmark"); // 数据项 let node = {   id: '123',   title: 'Google'   url: 'www.google.com'   tags: ['search', 'tool'] }; let request = bookmark.add(node); // 向对象库中添加数据项 // 监听请求成功事件 request.onsuccess = function() {   console.log("Bookmark added to the store", request.result); }; // 监听请求失败事件 request.onerror = function() {   console.log("Error", request.error); }; 复制代码

删除特定的数据需要指定查询条件,使用方法 delete(query)

// 删除键值满足 id='123' 的数据 bookmark.delete('123'); 复制代码

搜索数据

数据库另一个重要功能是搜索数据,IndexedDB 支持两种主要的搜索类型:

  • 基于一个键或范围进行搜索

  • 基于一个索引或范围进行搜索

两者的区别是:由于每个数据的键 key 是特殊唯一的,如果基于单一值搜索,最多获得一个数据项;而索引值可以复用,如果基于单一索引值搜索,可以获得多个数据项。

使用方法 get(query) 按键 key 搜索数据,返回满足条件的数据项;使用方法 getKey(query) 按键 key 搜索数据,但是返回的是满足条件的数据项的键值

bookmark.get('123') // 返回数据项 bookmark.getKey('123') // 返回 123 复制代码

???? 使用方法 getAll([query], [count])getAllKeys([query], [count]) 获取一个范围的数据或键值,返回数组,其中参数 query 表示一个范围,需要通过调用相应的函数创建。

???? 只返回键值的方法效率更高,该操作不需要解析读取完整的数据

通过索引 index 进行查询,也是使用相同的方法,但是通过索引对象调用这些方法。

let tagsIndex = bookmark.index("tags"); // 索引对象 tagsIndex.getAll('tool'); // 获取所有标记有 tool 的数据项


作者:Benbinbin
链接:https://juejin.cn/post/7018820211188957192


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐