js中数组filter过滤奇偶数_[译]玩转 JS 数组的 .filter 方法

.filter 是数组内置的迭代方法,它接收一个断言函数,这个函数会在迭代的每个数组成员上调用,如果函数的返回值是真值,就过滤出(即保留)这个成员,否则(是假值的话)就过滤掉这个成员。最终 .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 得来的、新的子集数组。这里需要注意的是执行 .filter 方法, restaurants 数组本身并不会改变。

下面这个 Codepen 笔记里,burgerJoints 就是过滤之后得到的数组 (点击查看):

否定断言

每一个断言,都有一个对应的否定断言。

断言是返回布尔值的函数。因为只有两个可能的布尔值,这意味着很容易“翻转”断言的值。

几个小时过去了,我饿了,我已经吃过汉堡了,现在想吃点别的,只要不是汉堡就行。一个选择就是从头编写一个 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 _只_在传统函数中可用。

当然,也可以这样搞(将返回函数写成箭头函数形式,用剩余参数运算符来接收参数)。

const negate = predicate => (...args) => !predicate(...args)

复制代码

返回断言

正如我们在 negate 函数中看到的那样,一个函数很容易在 JavaScript 中返回一个新函数。这对于编写“断言创建器”非常有用。我们回顾一下 isBurger 和 isPizza 断言。

const isBurger = ({cuisine}) => cuisine === 'Burger';

const isPizza = ({cuisine}) => cuisine === 'Pizza';

复制代码

这两个断言不是互为否定的,而是具有相同的判断逻辑,不同的仅是在比较的值上。所以我们可以把这两个函数合成一个 isCuisine 函数:

const isCuisine = comparision => ({cuisine}) => cuisine === comparision;

const isBurger = isCuisine('Burger');

const isPizza = isCuisine('Pizza');

复制代码

这很好!现在,如果我们需要过滤价格呢?

const isPrice = comparision => ({price}) => price === comparision;

const isCheap = isPrice('Cheap');

const isExpensive = isPrice('Expensive');

复制代码

现在 isCheap 和 isExpensive 是 DRY 的,isPazza 和 isBurger 也是 DRY 的——但是 isPrice 和 isCuisine 有重复的逻辑代码! 幸运的是,我们还可以进一步抽象。

const isKeyEqualToValue = key => value => object => object[key] === value;

// 这些可以重写

const isCuisine = isKeyEqualToValue('cuisine');

const isPrice = isKeyEqualToValue('price');

// 这些不需要改变了

const isBurger = isCuisine('Burger');

const isPizza = isCuisine('Pizza');

const isCheap = isPrice('Cheap');

const isExpensive = isPrice('Expensive');

复制代码

对我来说,这就是箭头函数的美妙之处。在一行中,你可以优雅地创建一个三阶函数。isKeyEqualToValue 是能返回 isPrice 的函数,同时它又是能返回 isCheap 的函数。

看,从原来的 restaurants 数组中创建多个过滤列表是多么容易。

组合断言

现在我们能过滤出有汉堡卖或者价格便宜的饭店, 但是如果想过滤出有便宜价格的汉堡饭店呢?一种选择是将两个 .filter 放在一起。

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 = restautants.filter(isCheapPizza);

复制代码

如果你想要披萨或汉堡都 OK 怎么办?

const both = (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 两个函数都接受一个断言数组,并返回一个断言函数。

由于所有这些断言都很容易由较高阶函数创建,因此根据用户的交互创建和应用这些断言并不困难。从我们学到的所有经验来看,这是一个应用程序的例子,它可以根据按钮点击来搜索餐馆。

总结

过滤器是 JavaScript 开发中的重要组成部分。无论是从 API 响应中找出数据,还是为了响应用户交互,都有很多次需要按条件获得一个数组子集的需求。我希望这篇文章能够帮助您理解 .filter 函数和使用断言,从而编写更可读和可维护的代码。

(完)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值