JavaScript .filter() 要领全剖析

.filter是一个内置的数组迭代要领,它接收一个“谓词(译者注: 指代一个过滤前提的函数)”,该“谓词”针对每一个值举行挪用,并返回一个相符该前提(“truthy值”)的数组。

上面那句话包括了许多信息,让我们来一一解答一下。

  • “内置”只是意味着它是言语的一部分 – 您不须要增加任何库来访问此功用。
  • “迭代要领”是指接收针对数组的每一个项运转的函数。.map和.reduce都是迭代要领的示例。
  • “谓词”是指.fiflter中接收的的函数。
  • “truthy值”是强迫转换为布尔值时盘算为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是new数组,它是餐馆的子集。值得注意的是,restaurants 这个数组是稳定。

下面是两个正在显现的列表的简朴示例 – 一个原始的餐馆数组,以及一个过滤的burgerJoints数组。

See the Pen .filter – isBurger by Adam Giese (@AdamGiese) on CodePen.

否认谓词

关于每一个谓词,都有一个相反的否认谓词。

谓词是一个返回布尔值的函数。由于布尔值只要true 和 false,这意味着很轻易“翻转”谓词的值。

我吃了汉堡已过了几个小时,如今又饿了。这一次,我想过滤out汉堡尝试新的东西。一种挑选是从头最先编写新的isNotBurger谓词。

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

然则,请检察两个谓词之间的类似水平。这不是 DRY code。另一种挑选是挪用isBurger谓词并翻转效果。

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

这个更好!假如汉堡的定义发生变化,您只须要在一个处所变动逻辑。然则,假如我们想要一些否认的谓词呢?由于这是我们能够常常想要做的事变,因而编写否认函数多是个好主意。

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?

MDN:

apply()要领挪用具有给定this的函数,并将参数作为数组(或类数组对象)供应。

什么是arguments?

MDN:

arguments对象是一切(非箭头)函数中可用的局部变量。您能够运用参数在函数内援用函数的参数object.

为何要运用旧的function,而不运用更酷的箭头函数?

在这类情况下,运用传统函数是必要的,由于arguments对象在传统函数上是_唯一_可用的。

到2018年8月20日。正如一些评论家所准确指出的那样, 你能够运用rest参数用[箭头函数写 negate ](https://css-tricks.com/level-…

返回谓词

正如我们在运用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(译者注:Don’t repeat yourself ,一种编程准绳,不也要写反复的代码),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');

对我来讲,这就是箭头功用之美。在一行中,您能够文雅地建立三阶函数。

看看从原始餐馆阵列建立多个挑选列表是何等轻易?

See the Pen .filter – returning predicates by Adam Giese (@AdamGiese) on CodePen.

撰写谓词

我们如今能够经由过程汉堡或低价的价钱过滤我们的阵列……然则假如你想要cheap burgers怎样办?一种挑选是将两个过滤器链接在一起。

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搜检数组的any成员是不是经由过程谓词。

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都接收一个谓词数组并返回一个谓词。

由于一切这些谓词都能够经由过程高阶函数轻松建立,因而依据用户的交互建立和运用这些谓词并不难题。综合我们学到的一切课程,这里是一个运用程序示例,经由过程运用基于按钮点击的过滤器来搜刮餐馆。

See the Pen .filter – dynamic filters by Adam Giese (@AdamGiese) on CodePen.

总结
过滤器是JavaScript开辟的主要组成部分。不管您是从API相应中挑选出毛病数据照样响运用户交互,您都邑无数次想要数组值的子集。我愿望这个概述有助于您能够操纵谓词来编写更易读和可保护的代码。

    原文作者:_小生_
    原文地址: https://segmentfault.com/a/1190000016192285
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞