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 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习惯的程序员对此不太适应,用起来比较别扭,可根据实际需求,使用适合自己的方法来实现相应的业务。