.filter
是一个内置的数组迭代方法,它接受一个谓词,该谓词针对其每个值调用,并返回所有返回真值的值的子集。
在一个语句中要解释这么多内容!让我们逐部分看看这个语句。
- “内置”简单地意味着它是语言的一部分——您无需添加任何库即可访问此功能。
- “迭代方法”接受一个函数,这些函数针对数组的每个项目运行。
.map
和.reduce
都是其他迭代方法的示例。 - “谓词”是一个返回布尔值的函数。
- “真值”是任何在强制转换为布尔值时计算结果为
true
的值。几乎所有值都是真值,除了:undefined
、null
、false
、0
、NaN
或""
(空字符串)。
要查看 .filter
的实际应用,让我们看看这个餐厅数组。
const restaurants = [
{
name: "Dan's Hamburgers",
price: 'Cheap',
cuisine: 'Burger',
},
{
name: "Austin's Pizza",
price: 'Cheap',
cuisine: 'Pizza',
},
{
name: "Via 313",
price: 'Moderate',
cuisine: 'Pizza',
},
{
name: "Bufalina",
price: 'Expensive',
cuisine: 'Pizza',
},
{
name: "P. Terry's",
price: 'Cheap',
cuisine: 'Burger',
},
{
name: "Hopdoddy",
price: 'Expensive',
cuisine: 'Burger',
},
{
name: "Whataburger",
price: 'Moderate',
cuisine: 'Burger',
},
{
name: "Chuy's",
cuisine: 'Tex-Mex',
price: 'Moderate',
},
{
name: "Taquerias Arandina",
cuisine: 'Tex-Mex',
price: 'Cheap',
},
{
name: "El Alma",
cuisine: 'Tex-Mex',
price: 'Expensive',
},
{
name: "Maudie's",
cuisine: 'Tex-Mex',
price: 'Moderate',
},
];
信息太多了。我目前想吃汉堡,所以让我们稍微过滤一下这个数组。
const isBurger = ({cuisine}) => cuisine === 'Burger';
const burgerJoints = restaurants.filter(isBurger);
isBurger
是谓词,burgerJoints
是一个新数组,它是 restaurants 的子集。需要注意的是,restaurants
在 .filter 之后保持不变。
这是一个简单的示例,展示了两个列表的渲染——一个来自原始 restaurants
数组,另一个来自经过过滤的 burgerJoints
数组。
查看 CodePen 上 Adam Giese 的作品 .filter – isBurger (@AdamGiese) 在 CodePen 上。
否定谓词
对于每个谓词,都有一个相等且相反的否定谓词。
谓词是一个返回布尔值的函数。由于只有两个可能的布尔值,这意味着很容易“翻转”谓词的值。
我吃汉堡已经几个小时了,现在我又饿了。这次,我想排除汉堡,尝试一些新的东西。一种选择是从头开始编写一个新的 isNotBurger
谓词。
const isBurger = ({cuisine}) => cuisine === 'Burger';
const isNotBurger = ({cuisine}) => cuisine !== 'Burger';
但是,看看这两个谓词之间有多少相似之处。这不是很 DRY 代码。另一种选择是调用 isBurger
谓词并翻转结果。
const isBurger = ({cuisine}) => cuisine === 'Burger';
const isNotBurger = restaurant => !isBurger(restaurant);
这样更好!如果汉堡的定义发生了变化,您只需要在一个地方更改逻辑即可。但是,如果我们有很多想要否定的谓词呢?由于这可能是我们经常想要做的事情,因此编写一个 negate
函数可能是一个好主意。
const negate = predicate => function() {
return !predicate.apply(null, arguments);
}
const isBurger = ({cuisine}) => cuisine === 'Burger';
const isNotBurger = negate(isBurger);
const isPizza = ({cuisine}) => cuisine === 'Pizza';
const isNotPizza = negate(isPizza);
您可能有一些问题。
什么是 .apply?
apply()
方法使用给定的this
值和作为数组(或类数组对象)提供的arguments
调用函数。
什么是 arguments?
arguments
对象是在所有(非箭头)函数中都可用的局部变量。您可以通过使用arguments
对象在函数内引用函数的参数。
function
而不是更新、更酷的箭头函数?
为什么返回旧式 在这种情况下,返回传统的 function
是必要的,因为 arguments
对象仅在传统函数上可用。
2018年8月20日添加。正如一些评论者正确指出的那样,您可以使用箭头函数编写 `negate`,方法是使用 rest 参数。
返回谓词
正如我们在 negate
函数中看到的那样,函数很容易在 JavaScript 中返回一个新函数。这对于编写“谓词创建器”很有用。例如,让我们回顾一下我们的 isBurger
和 isPizza
谓词。
const isBurger = ({cuisine}) => cuisine === 'Burger';
const isPizza = ({cuisine}) => cuisine === 'Pizza';
这两个谓词共享相同的逻辑;它们仅在比较方面有所不同。因此,我们可以将共享的逻辑包装在一个 isCuisine
函数中。
const isCuisine = comparison => ({cuisine}) => cuisine === comparison;
const isBurger = isCuisine('Burger');
const isPizza = isCuisine('Pizza');
这很棒!现在,如果我们想开始检查价格呢?
const isPrice = comparison => ({price}) => price === comparison;
const isCheap = isPrice('Cheap');
const isExpensive = isPrice('Expensive');
现在 isCheap
和 isExpensive
是 DRY 的,isPizza
和 isBurger
是 DRY 的——但 isPrice
和 isCuisine
共享它们的逻辑!幸运的是,对于可以返回多少个函数没有限制。
const isKeyEqualToValue = key => value => object => object[key] === value;
// these can be rewritten
const isCuisine = isKeyEqualToValue('cuisine');
const isPrice = isKeyEqualToValue('price');
// these don't need to change
const isBurger = isCuisine('Burger');
const isPizza = isCuisine('Pizza');
const isCheap = isPrice('Cheap');
const isExpensive = isPrice('Expensive');
在我看来,这就是箭头函数的魅力所在。在一行代码中,您可以优雅地创建一个三阶函数。isKeyEqualToValue
是一个返回函数 isPrice
的函数,而 isPrice
又返回函数 isCheap
。
看看从原始 restaurants
数组创建多个过滤列表有多容易?
查看 CodePen 上 Adam Giese 的作品 .filter – returning predicates (@AdamGiese) 在 CodePen 上。
组合谓词
我们现在可以根据汉堡或便宜的价格过滤我们的数组……但是如果你想要便宜的汉堡呢?一种选择是将两个过滤器链接在一起。
const cheapBurgers = restaurants.filter(isCheap).filter(isBurger);
另一种选择是将这两个谓词“组合”成一个。
const isCheapBurger = restaurant => isCheap(restaurant) && isBurger(restaurant);
const isCheapPizza = restaurant => isCheap(restaurant) && isPizza(restaurant);
看看所有重复的代码。我们绝对可以将其包装到一个新函数中!
const both = (predicate1, predicate2) => value =>
predicate1(value) && predicate2(value);
const isCheapBurger = both(isCheap, isBurger);
const isCheapPizza = both(isCheap, isPizza);
const cheapBurgers = restaurants.filter(isCheapBurger);
const cheapPizza = restaurants.filter(isCheapPizza);
如果你对披萨或汉堡都可以接受怎么办?
const either = (predicate1, predicate2) => value =>
predicate1(value) || predicate2(value);
const isDelicious = either(isBurger, isPizza);
const deliciousFood = restaurants.filter(isDelicious);
这是一个朝着正确方向迈出的步骤,但是如果你有多于两种食物想要包含呢?这不是一个非常可扩展的方法。这里有两个内置的数组方法非常有用。.every
和 .some
都是谓词方法,也接受谓词。.every
检查数组的每个成员是否通过谓词,而 .some
检查数组的任何成员是否通过谓词。
const isDelicious = restaurant =>
[isPizza, isBurger, isBbq].some(predicate => predicate(restaurant));
const isCheapAndDelicious = restaurant =>
[isDelicious, isCheap].every(predicate => predicate(restaurant));
并且,像往常一样,让我们将它们包装到一些有用的抽象中。
const isEvery = predicates => value =>
predicates.every(predicate => predicate(value));
const isAny = predicates => value =>
predicates.some(predicate => predicate(value));
const isDelicious = isAny([isBurger, isPizza, isBbq]);
const isCheapAndDelicious = isEvery([isCheap, isDelicious]);
isEvery
和 isAny
都接受一个谓词数组并返回一个谓词。
由于所有这些谓词都可以通过高阶函数轻松创建,因此根据用户的交互创建和应用这些谓词并不太困难。结合我们学到的所有课程,这是一个根据按钮点击应用过滤器的餐厅搜索应用程序示例。
查看 CodePen 上 Adam Giese 的作品 .filter – dynamic filters (@AdamGiese) 在 CodePen 上。
总结
过滤器是 JavaScript 开发中不可或缺的一部分。无论您是在从 API 响应中筛选出错误数据,还是在响应用户交互,都有无数次您需要数组值的子集。我希望这篇概述有助于您了解如何操作谓词以编写更易读和易于维护的代码。
字符串的比较不是区分大小写的吗?在我看来,(‘Burger’===’burger’) 应该返回 false。
你完全正确——我刚刚浏览并将其全部更正为大写。
当然,“negate”也可以这样写
const negate = method => (…args) => !method(…args);
同样的,isEvery()和其他类似的项目也是如此,使 arguments 中隐含的数组
const isEvery = (…predicates) => value => predicates.every(p => p(value));
const isCheapAndDelicious = isEvery(isCheap, isDelicious);
特别是在你使用解构的情况下,rest/spread 在任何地方都受支持。(如果不是,你可能在某个时候通过 Babel 处理你的代码,它会将它转换为 es3/5 代码。)
关于箭头函数与构造函数——
您可以使用
spread
语法访问箭头函数中的参数。所以你的negate函数变成
事实上,我相信ES6已经完全淘汰了它们。
你完全正确——我在那一部分添加了一条注释,链接到此示例。
为什么在将cuisine变量用作函数的参数时,要使用花括号将其括起来?
我正在使用对象解构直接在参数上。
例如
等同于
那是不正确的,你解构了两次
此外,正如其他人所说,当你可以使用
...spreading
时,不要使用arguments
。抓住了!已修复
感谢您对
filter
和函数组合的清晰简洁的介绍。我将与团队中的其他JavaScript开发人员分享这篇文章。