这两天看了一下后端给的接口文档,每一个都要求筛选,而且这个筛选还是多条件的,还是不能固定的,要求根据用户的输入然后筛选,我之前的实现大概是这样子,当用户想要筛选的时候就去检索条件,并输入相关的内容进行筛选
所以这种策略的话,就是需要实现以下两个核心点
- 监听输入
- 筛选数据
接下来就会针对这两点进行一个分析
监听输入
因为是动态内容,因此需要监听输入框的数值改变,当用户的输入停止时或者说失去了焦点,这个时候进行一个筛选,那么监听输入这个就有点来头了,虽然我用的是 vue
, vue
有着响应式数据的加持,但是这个用响应式不太行,为什么呢?因为我这个的设计上是没有一个确定
键的,不像百度,输入完搜索字符串然后就可以回车或者点搜索按钮
,但我这个就有点讲究,这是一个实时响应的搜索,到这里又有点像百度,因为我们在百度搜索的时候输入部分关键字,可能你需要的结果就已经出现了,比如下面这样
本来想搜掘金的,只打了个掘
怎么就有提示信息了呢?是因为我们输入掘
的时候就已经发送请求到服务端了,服务端返回了结果并渲染,但是百度是这方面的专家,它返回检索信息的速度快到让人以为它其实监听的 <input>
是输一个字符就发一次请求,像普通人的服务器还是要做一下优化的,不然搜的太快,内容不是用户想要的,浪费了性能,增加了服务器压力,看到这里你可能已经懂了,优化方案就监听输入
方面要讲的主角,防抖和节流
防抖节流两兄弟多少都听过一些,最简单的介绍就是
- 间隔时间内只执行一次
- 间隔时间内执行
防抖/节流
那么防抖用在输入框这个场景适不适合呢?虽然防抖节流定义是这么说,但是面对不同的场景,还是需要不同的拟像化描述一下这个场景,想象一下
- 面对狂风骤雨般的输入,等到用户累了,不想输入后达到 1s 再发送请求合适吗?
- 面对狂风骤雨般的输入,每到 1s 后就发送一次请求合适吗?
这时候就要思考你要面对用户的场景了,是满足用户好奇宝宝的心态,设置一个比较小的阈(yu)值呢?还是培养用户的耐心,就得等你停止输入才能给你答案
加锁
讲了这么多,你可能在想我最后选用了什么?其实我是监听了 <el-input>
的 change 事件
为什么说它是加锁呢?因为这个 change
事件是只有你 input
失去焦点或者按下回车后才会触发,相当于只有失去焦点和按下回车这两把钥匙之一才能够打开 change 这个锁,像一些按钮面对短时间内大量的点击事件也未必要上防抖节流,简单的等待后端返回响应后再解锁
然后恢复点击状态
筛选数据
面对多个条件的后端接口,在前端可以一次性请求回来并进行筛选,然后得到最终的数据,其实有一个就是可以基本无视筛选算法好坏的解决方案
HTML5 Web Workers
这个其实有点 Android 内味了, Android 其实是不能在渲染线程中操作数据的,得另开一个线程,类比到 web 页面这里,大量的 JS 计算会阻塞页面的渲染和交互,如果有个另一个线程给我跑筛选不就行了, Web Workers 就是干这事的,甭管什么 O(nlogn), O(n^2), O(n), 怎么整都不会影响你的页面
善用 JS API
让我们看看普通人写筛选是怎么样的,每次筛出一部分然后继续拿筛好的和剩下的继续筛,无脑套好几个 for 循环
// filter
const filterChange = async () => {
let nameFilterList: People[] = [];
let idFilterList: People[] = [];
let res: People[] = [];
// 获取数据
const allFilterlist = [nameFilterList, idFilterList];
for (let i = 0; i < allFilterlist.length; i++) {
if (allFilterlist[i].length > 0 && res.length > 0) {
allFilterlist[i].forEach((i) => {
const temp: People[] = [];
res.forEach((j) => {
if (i.number === j.number) {
temp.push(j);
}
});
res = temp;
});
} else if (allFilterlist[i].length > 0 && res.length === 0) {
res.push(...allFilterlist[i]);
}
}
list.value = res;
};
其实完全可以 JS 新增的 Set API 来解决这个问题,筛选,本质上就是求交集,求交集用 Hash 的思想就能够做到 O(n+m) 的时间复杂度, 先遍历一遍筛选条件1对应数组
, 将唯一值,因为我们数组元素往往都是一个复杂的对象,可以将对象中的唯一值抽出来做 Hash 达到减少空间复杂度的目的, 比如 ID,代码如下
const allFilterlist = [nameFilterList, idFilterList];
for (let i = 0; i < allFilterlist.length; i++) {
if (allFilterlist[i].length > 0 && res.length > 0) {
res = intersect(allFilterlist[i], res);
} else if (allFilterlist[i].length > 0 && res.length === 0) {
allFilterlist[i].forEach((item) => set.add(item.name));
res.push(...allFilterlist[i]);
}
}
const intersect = (arr1: People[], arr2: People[]): People[] => {
const set = new Set();
const res: People[] = [];
arr1.forEach((item) => set.add(item.name));
arr2.forEach((item) => {
if (set.has(item.name)) {
res.push(item);
}
});
return res;
};
时间复杂度将由原来的 O(n*m*p)
变为 O(n*(m+p))
,算是减少了一个量级
总结
筛选这块遇到的难点,其实就是筛选数据优化这块,其实这种东西能用就行,但还是可能出于自己的盲目自信,老是想着优化,然后就把大把的时间给浪费了