IndexedDB是浏览器提供的本地数据库,它可以被网页脚本创建和操作。IndexedDB允许存储大量数据,提供查找接口,还能建立索引。这些都是LocalStorage或Cookie不具备的。就数据库类型而言,IndexedDB不属于关系型数据库(不支持SQL查询语句),更接近NoSQL数据库。
这篇将讲解下关于IndexedDB的条件筛选,通过内置功能函数,相对增加查询速度。在之前一篇中,是通过在utils.js工具包中定义分页函数,来实现分页的,这里我们将通过IndexedDB提供的相关函数来实现分页功能。
效果如下图:

IDBObjectStore接口的getAll()方法返回一个IDBRequest对象,该对象包含对象存储中与指定参数匹配的所有对象,如果没有给出参数,则返回存储中的所有对象。
如果成功找到一个值,则创建该值的结构化克隆,并将其设置为请求对象的结果。
此方法产生相同的结果:
如果要分别获取,可以使用以下方法:
语法:
- getAll()
- getAll(query)
- getAll(query, count)
参数:
| 名称 | 描述 |
|---|---|
| query | 要查询的键或IDBKeyRange。如果不传递任何信息,则默认为选择此对象存储中的所有记录的键范围。 |
| count | 指定如果找到多个值要返回的值的数量。如果它小于0或大于2^32 - 1,则会抛出TypeError异常。 |
返回值:
一个IDBRequest对象,在该对象上触发与此操作相关的后续事件。
这里是通过getAll()函数,一次拿到所有数据,再通过utils工具类中genderPage()函数对数据进行筛选分页。
原方utils/utils.js工具包中方法:
- /**
- * 生成分页数据
- */
- export const genderPage = (data, param) => {
- //判断分页数据是否存在,否则赋值默认参数
- param = param && 'undefined'!==typeof param['page'] && 'undefined'!==typeof param['pageSize'] ? param : {
- page: 1,
- pageSize: 10
- }
- let newData = data.map(item => item),
- start = (param.page - 1) * param.pageSize,
- end = newData.length - start < param.pageSize ? newData.length : start + param.pageSize;
- return newData.slice(start, end);
- }
原数据库操作类db/model/student.js分页查询函数代码如下:
- /**
- * 获取 学员列表 - 分页模式
- * @param name 查询关键词
- * @param param 分页参数
- */
- export const loadStudentPage = (name, param) => {
- return new Promise((resolve, reject) => {
- //打开游标
- let {store} = openTransactionIndex(storeName);
- //获取所有数据
- let alls = store.getAll();
-
- alls.onerror = function(e){
- reject(
- rJson(0, e, '查询出错了~')
- );
- }
-
- alls.onsuccess = function(e){
- let result = e.target.result;
- if(result){
- let rData = result;
- if(name){
- rData = result.filter(item => item.name.includes(name));
- }
-
- getClassByIdsCursor(rData.filter(item => item.cid).map(item => item.cid)).then(res => {
- rData = rData.map(item => {
- if('undefined'!==typeof res[item.cid]){
- item['classify'] = res[item.cid]['name'];
- }
- return item;
- })
-
- //通过genderPageData函数进行分页处理
- resolve(
- rJson(1, {
- list: genderPage(rData, param),
- total: rData.length
- }, '获取成功~')
- )
- }).catch(e => {
- reject(
- rJson(0, e, '查询出错了~')
- )
- });
-
- }else{
- reject(
- rJson(0, null, '未查询到数据~')
- )
- }
- // console.log('store', result);
- }
-
- });
- }
以上方法每次切换分页,都需要一次获取所有表中数据,获取到数据后,再进行分页处理。这种方法在数据量不大情况下可以使用,一旦数据量过大则查询非常慢,这里getAll()函数已提供了相应参数,具体入参前面已列出。
我们通过以下两步即可改造好分页功能,具体如下:
第一步:打开db/model/student.js,修改loadStudentPage()函数,使用getAllKeys()获取所有索引,通过分页参数计算出分页起始位置ID,再通过IDBKeyRange.lowerBound指定查询起始位置,代码如下:
- /**
- * 获取 学员列表 - 分页模式
- * @param param 参数
- * @description 通过IndexedDB内置函数实现分页
- */
- export const loadStudent2Page = param => {
- //判断分页数据是否存在,否则赋值默认参数
- param = param && 'undefined'!==typeof param['page'] && 'undefined'!==typeof param['pageSize'] ? param : {
- page: 1,
- pageSize: 10
- };
-
- return new Promise((resolve, reject) => {
- //打开游标
- let {store} = openTransactionIndex(storeName);
- //获取所有keys
- let keys = store.getAllKeys();
- //获取当前表数量总条数
- let countResult = store.count();
-
- keys.onerror = function(e){
- reject(
- rJson(0, e, '查询出错了~')
- );
- }
-
- keys.onsuccess = function(e){
- let allKeys = e.target.result;
- //获取当前分页开始位置ID
- let start = (param.page - 1) * param.pageSize;
- //获取分页起始位
- let lowerKey = IDBKeyRange.lowerBound(allKeys[start]);
- //获取分页数据
- let alls = store.getAll(lowerKey, param.pageSize);
-
- alls.onerror = function(e){
- reject(
- rJson(0, e, '查询出错了~')
- );
- }
-
- alls.onsuccess = function(e){
- let result = e.target.result;
- if(result){
- //获取班级信息
- getClassByIdsCursor(result.filter(item => item.cid).map(item => item.cid)).then(res => {
- result = result.map(item => {
- if('undefined'!==typeof res[item.cid]){
- item['classify'] = res[item.cid]['name'];
- }
- return item;
- })
-
- //通过genderPageData函数进行分页处理
- resolve(
- rJson(1, {
- list: result,
- total: countResult.result,
- }, '获取成功~')
- )
- }).catch(e => {
- reject(
- rJson(0, e, '查询出错了~')
- )
- });
-
- }else{
- reject(
- rJson(0, null, '未查询到数据~')
- )
- }
-
- }
- }
- //keys end
-
- });
- }
第三步:打开/db/api/index.js,添加loadStudent2Page()请求,代码如下:
- import {
- loadStudent2Page
- } from '@/db/model/student'
-
- /**
- * 获取分页数据,不带筛选
- */
- export const getStudentPage2List = (param) => {
- return loadStudent2Page(param);
- }
第三步:打开src/pages/student/index.vue,修改updateList()函数;引入接口getStudentPage2List()函数,列表页代码如下:
- updateList(){
- getStudentPage2List({
- page: this.page,
- pageSize: this.pageSize
- }).then(res => {
- if(res.code==1){
- this.pageTotal = res.data['total'];
- this.tableList = res.data['list'].map(item => {
- item['createtime'] = formatDate(item.createtime);
- item['updatetime'] = formatDate(item.updatetime);
- if(item['avatar']){
- item.avatar = URL.createObjectURL(item.avatar);
- }
- return item;
- });
- }
- }).catch(e => {
- console.error(e);
- })
- }
实现后效果如下:

此时,切换分页效果和使用utils工具包中genderPage()函数是一样的,而且是在指定范围内查询,遇到数据量大的情况下,速度得到大大的提升。
想必在前面章节中,大家就应该发现数据是正序排列的,最新数据一直排在后面,我们可以在“二、getAll实现分页”的基本上,稍微调整下即可实现倒叙排列。
原代码如下:
- let allKeys = e.target.result;
- //获取当前分页开始位置ID
- let start = (param.page - 1) * param.pageSize;
- //获取分页起始位
- let lowerKey = IDBKeyRange.lowerBound(allKeys[start]);
将以上代码,先把allKeys作个反转,通过分页参数计算出当前分页区间,再将获取的开始和结束位置的索引,赋值到IDBKeyRange.bound中即可,修改后代码如下:
- let allKeys = e.target.result.reverse();
- //获取当前分页开始位置ID
- let end = (param.page - 1) * param.pageSize,
- start = (allKeys.length - end < param.pageSize ? allKeys.length : end + param.pageSize) - 1;
-
- //获取分页起始位
- let lowerKey = IDBKeyRange.bound(allKeys[start], allKeys[end]);
此时会发现列表中显示数据,已经是倒叙查询出来的,但是数据还是以正序结果在显示。正常情况应该是“刘博”排在第一位,但是现在是”陈可“,如下图:

其实解决这个问题很简单,还是将最终结果反转一下即可,result后面添加reverse()函数,代码如下:
- resolve(
- rJson(1, {
- list: result.reverse(),
- total: countResult.result,
- }, '获取成功~')
- )
此时显示则为倒序排列了,如下图:

IDBObjectStore 接口的 openCursor ()方法返回一个 IDBRequest 对象,并在一个单独的线程中返回一个新的 IDBCursorWithValue 对象。用于使用游标遍历对象存储区。
语法:
- openCursor()
- openCursor(query)
- openCursor(query, direction)
参数:
| 名称 | 描述 |
|---|---|
| query | 要查询的键或IDBKeyRange。如果传递了一个有效键,则默认为只包含该键的范围。如果不传递任何信息,则默认为选择此对象存储中的所有记录的键范围。 |
| direction | 指示光标移动方向的字符串。默认值是next。有效值: next:游标在存储的开始处打开;然后,光标按键的递增顺序返回所有记录,甚至是重复的记录。 nextunique:游标在存储的开始处打开;然后,游标按键的递增顺序返回所有非重复的记录。 prev:游标在存储的开始处打开;然后,光标按键的递减顺序返回所有记录,甚至是重复的记录。 prevunique:游标在存储的开始处打开;然后,游标按键的递减顺序返回所有不重复的记录。 |
返回值:
一个IDBRequest对象,在该对象上触发与此操作相关的后续事件。
写到这里有些人可能还未发现,改造完后的列表中关键词条件筛选已失效了。细心朋友应该已经发现getAll()无法满足模糊条件查询,此时我们改用openCursor()游标来实现 模糊分页查询功能。使用openCursor()方便之处在于倒序查询直接可以通过它来实现了,上面已列出了入参值,在第二个参数位置填入”prev”即可。
列表页pages/student/index.vue中updateList()函数中添加name关键词,代码如下:
- /**
- * 查询数据
- */
- searchEvent(){
- this.page = 1;
- this.updateList();
- },
- /**
- * 获取列表数据
- */
- updateList(){
- getStudentPage2List({
- page: this.page,
- pageSize: this.pageSize,
- name: this.keyword
- }).then(res => {
- if(res.code==1){
- this.pageTotal = res.data['total'];
- this.tableList = res.data['list'].map(item => {
- item['createtime'] = formatDate(item.createtime);
- item['updatetime'] = formatDate(item.updatetime);
- if(item['avatar']){
- item.avatar = URL.createObjectURL(item.avatar);
- }
- return item;
- });
- }
- }).catch(e => {
- console.error(e);
- })
- },
打开db/model/student.js,修改loadStudentPage()函数,代码如下:
- /**
- * 获取 学员列表 - 分页模式
- * @param param 参数
- * @description 通过IndexedDB内置函数实现分页,倒序查询,以及关键词模糊查询
- */
- export const loadStudent4Page = param => {
- //判断分页数据是否存在,否则赋值默认参数
- param = param && 'undefined'!==typeof param['page'] && 'undefined'!==typeof param['pageSize'] ? param : {
- page: 1,
- pageSize: 10,
- name: ""
- };
-
- return new Promise((resolve, reject) => {
- //打开游标
- let {store} = openTransactionIndex(storeName);
- //返回结果集
- let rData = [];
- //结果区间
- let start = (param.page - 1) * param.pageSize,
- end = start + param.pageSize,
- index = 0;
-
- store.openCursor(null, 'prev').onsuccess = function(e){
- let cursor = e.target.result;
- if(cursor){
- //判断关键词是否被包含
- if(cursor.value.name.includes(param.name)){
- if(index>=start&&index
- rData.push(cursor.value);
- }
- index++;
- }
-
- cursor.continue();
- }else{
- //获取班级信息
- getClassByIdsCursor(rData.filter(item => item.cid).map(item => item.cid)).then(res => {
- rData = rData.map(item => {
- if('undefined'!==typeof res[item.cid]){
- item['classify'] = res[item.cid]['name'];
- }
- return item;
- })
-
- //通过genderPageData函数进行分页处理
- resolve(
- rJson(1, {
- list: rData,
- total: index,
- }, '获取成功~')
- )
- }).catch(e => {
- reject(
- rJson(0, e, '查询出错了~')
- )
- });
-
- }
-
- }
-
- });
- }
此时我们在搜索框中输入“刘”,则可以模糊查询出结果了,如下图:


总结:
不管是通过getAll()一次性获取所有数据,还是通过IDBKeyRange实现条件查询,或是通过openCursor()游标查询,实现过程中都遇到短板。其实仔细分析新增的loadStudent4Page()函数,虽然使用openCursor()游标查询,已通过start和end将分页数据查询完成,但还需要将所有数据遍历一次,来获取当前模糊查询结果的总条数。相比getAll()函数一次获取再通过utils工具包中genderPage()实现分页,性能是相差不大的。
由于IndexedDB是非关系性数据库,使用SQL习惯的程序员对此不太适应,用起来比较别扭,可根据实际需求,使用适合自己的方法来实现相应的业务。