javascript - 如何根据对象过滤对象数组?
我有一个对象数组:
let response = [{"id": 1, "name": "Alise", "price": 400, "category": 4}];
另外,我还有一些用于过滤的数组:
let names = ["Jessy", "Megan"];
let prices = [300, 500];
let category = [1,2,4];
我需要根据这些数组来过滤数组,条件是在 names, prices, category 之间使用 AND,并且在每个数组的元素之间使用 OR 条件:Jessy OR Megan 等等
我做了这个解决方案:
const filterByCategory = (response: IResponse[], filterBy: any[]) => response.filter((e: IResponse) => filterBy.indexOf(e.category) > -1);
const filterByPrice = (response: IResponse[], filterBy: any[]) => response.filter((e: IResponse) => e.price in filterBy);
然后我不知道如何让通话更好:
一种方式:
filter() {
let filtered = filterByCategory(response, category);
filtered = filterByPrice(filtered, prices);
}
第二:
filter() {
let filtered = [];
if (category && category.length) {
filtered = filterByCategory(response, category);
}
if (prices && prices.length) {
filtered = filterByCategory(filtered, category);
}
}
第三:
let filtered = response.filter((element) => {
return category && category.indexOf(e.category) > -1 &&
prices && prices.indexOf(e.price) > -1 && etc.
});
- 首先是简单修改过滤逻辑,而不更改主过滤器
- 第二个和第一个一样,尽管进行了额外检查
- 第三种方式是最短的,但将来难以修改
答案#1
像这样的东西是清晰、简洁且易于维护的。
const filteredItems = response.filter(e => {
const validations = {
containsCategory: category && category.includes(e.category),
containsPrice: prices && prices.includes(e.price),
containsName: names && names.includes(e.name),
};
return Object.values(validations).every(v => v);
});
你可以在这里使用一个对象,这样你不仅可以获得为验证命名的好处,还可以迭代它们并检查每个 validation === true。如果你把每个验证定义为变量,那么你将有重复的代码。例如
const filteredItems = response.filter(e => {
const containsCategory = category && category.includes(e.category);
const containsPrice = prices && prices.includes(e.price);
const containsName = names && names.includes(e.name);
return containsName && containsPrice && containsName;
});
每次你想添加一个验证时,你必须在两个地方更新名称(返回值和定义)。
重要的是要注意,虽然使用你发布的代码可以用更简洁的方式实现相同的结果,但这种方法可以让你在未来轻松添加更多的验证类别——而更简洁的解决方案则做不到这一点。
例如,你也可以轻松地向 validations 添加字符串过滤器或高级过滤功能:
const validations = {
matchesSearchString: !searchString || e.name.toLowerCase().startsWith(searchSting.toLowerCase()),
isAnAncientQueen: isAnAncientQueen(e.name),
/* -- snip -- */
};
答案#2
我会说其他答案更简洁,但如果你能够利用 TypeScript 的类型系统来做出一个安全且非常通用的解决方案,这也是可行的。是否采用这种方法取决于你的具体需求,尤其是关于类型安全和可维护性方面。
我的通用方法是定义一种可以应用于任何对象的过滤器类型,然后实现一个能够将过滤器应用于任意类型数组的函数。
对于“filter”,这将有效:
type Filter<T> = {
[P in keyof T]?: (T[P])[]
}
你可以将其理解为“对于 T 类型的每个成员 X,Filter<T> 将有一个可选的同名数组,类型为 X[]。”
然后过滤函数变为:
function filter<T>(items: T[], filter: Filter<T>): T[]{
// interim result for easier debugging
let result =
items.filter(
item => {
// check each member in the filter
for (let key in filter) {
let valueInFilter = filter[key];
// if it's empty, then it's OK
if (valueInFilter) {
// otherwise the value on item MUST be found in the array on filter
let valueInItem = item[key];
if (valueInFilter.indexOf(valueInItem) == -1) {
return false;
}
}
}
// if every check passes, keep the item
return true;
}
);
return result;
}
把所有的东西放在一起,看起来就像这样:
let responses: IResponse[] = [
{ "id": 1, "name": "Alise", "price": 400, "category": 4 },
{ "id": 2, "name": "Bob", "price": 300, "category": 2 }
];
let someFilter: Filter<IResponse> = {
id: [1, 2, 4],
price: [300]
};
console.log(filter(responses, someFilter))
我在 TypeScript Playground 上整理了这个,所以你可以看到所有的类型检查都可以正常工作。
