Level up your .filter game

Avatar of Adam Giese
Adam Giese am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

.filter ist eine integrierte Array-Iterationsmethode, die ein Prädikat akzeptiert, das für jeden seiner Werte aufgerufen wird, und eine Teilmenge aller Werte zurückgibt, die einen wahrheitsgemäßen Wert ergeben.

Das ist viel auf einmal zu verarbeiten! Sehen wir uns diese Aussage Stück für Stück an.

  • „Eingebaut“ bedeutet einfach, dass es Teil der Sprache ist – Sie müssen keine Bibliotheken hinzufügen, um auf diese Funktionalität zuzugreifen.
  • „Iterationsmethoden“ akzeptieren eine Funktion, die für jedes Element des Arrays ausgeführt wird. Sowohl .map als auch .reduce sind weitere Beispiele für Iterationsmethoden.
  • Ein „Prädikat“ ist eine Funktion, die einen booleschen Wert zurückgibt.
  • Ein „truthy value“ ist jeder Wert, der zu true ausgewertet wird, wenn er zu einem booleschen Wert umgewandelt wird. Fast alle Werte sind truthy, mit Ausnahmen von: undefined, null, false, 0, NaN oder "" (leerer String).

Um .filter in Aktion zu sehen, betrachten wir dieses Array von Restaurants.

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',
    },
];

Das sind viele Informationen. Ich bin gerade in der Stimmung für einen Burger, also filtern wir dieses Array ein wenig.

const isBurger = ({cuisine}) => cuisine === 'Burger';
const burgerJoints = restaurants.filter(isBurger);

isBurger ist das Prädikat und burgerJoints ist ein neues Array, das eine Untermenge von Restaurants ist. Es ist wichtig zu beachten, dass restaurants von .filter unverändert blieb.

Hier ist ein einfaches Beispiel für das Rendern zweier Listen – einer des ursprünglichen restaurants-Arrays und einer des gefilterten burgerJoints-Arrays.

Siehe den Pen .filter – isBurger von Adam Giese (@AdamGiese) auf CodePen.

Prädikate negieren

Für jedes Prädikat gibt es ein gleichwertiges und entgegengesetztes negiertes Prädikat.

Ein Prädikat ist eine Funktion, die einen booleschen Wert zurückgibt. Da es nur zwei mögliche boolesche Werte gibt, ist es einfach, den Wert eines Prädikats zu „flippen“.

Ein paar Stunden sind vergangen, seit ich meinen Burger gegessen habe, und jetzt bin ich wieder hungrig. Dieses Mal möchte ich Burger ausschließen, um etwas Neues auszuprobieren. Eine Möglichkeit ist, ein neues isNotBurger-Prädikat von Grund auf neu zu schreiben.

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

Betrachten Sie jedoch die Ähnlichkeiten zwischen den beiden Prädikaten. Das ist kein sehr DRYer Code. Eine andere Möglichkeit ist, das isBurger-Prädikat aufzurufen und das Ergebnis zu flippen.

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

Das ist besser! Wenn sich die Definition eines Burgers ändert, müssen Sie die Logik nur an einer Stelle ändern. Was aber, wenn wir eine Reihe von Prädikaten haben, die wir negieren möchten? Da dies etwas ist, das wir wahrscheinlich oft tun möchten, kann es eine gute Idee sein, eine negate-Funktion zu schreiben.

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);

Sie haben vielleicht einige Fragen.

Was ist .apply?

MDN

Die Methode apply() ruft eine Funktion mit einem gegebenen this-Wert und arguments auf, die als Array (oder Array-ähnliches Objekt) übergeben werden.

Was sind arguments?

MDN

Das Objekt arguments ist eine lokale Variable, die in allen (nicht-Pfeil-)Funktionen verfügbar ist. Sie können auf die Argumente einer Funktion innerhalb der Funktion mit dem Objekt arguments verweisen.

Warum eine „Old-School“-function anstelle einer neueren, cooleren Pfeilfunktion zurückgeben?

In diesem Fall ist die Rückgabe einer traditionellen function notwendig, da das arguments-Objekt nur in traditionellen Funktionen verfügbar ist.

Hinzugefügt am 20. August 2018. Wie einige Kommentatoren richtig angemerkt haben, kann man `negate` mit einer Pfeilfunktion schreiben, indem man Rest-Parameter verwendet.

Prädikate zurückgeben

Wie wir bei unserer negate-Funktion gesehen haben, ist es in JavaScript einfach, dass eine Funktion eine neue Funktion zurückgibt. Dies kann nützlich sein, um „Prädikaterzeuger“ zu schreiben. Betrachten wir zum Beispiel unsere isBurger und isPizza Prädikate.

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

Diese beiden Prädikate haben die gleiche Logik; sie unterscheiden sich nur in den Vergleichen. Wir können also die gemeinsame Logik in eine isCuisine-Funktion verpacken.

const isCuisine = comparison => ({cuisine}) => cuisine === comparison;
const isBurger  = isCuisine('Burger');
const isPizza   = isCuisine('Pizza');

Das ist großartig! Was nun, wenn wir anfangen, den Preis zu prüfen?

const isPrice = comparison => ({price}) => price === comparison;
const isCheap = isPrice('Cheap');
const isExpensive = isPrice('Expensive');

Jetzt sind isCheap und isExpensive DRY, und isPizza und isBurger sind DRY – aber isPrice und isCuisine teilen ihre Logik! Glücklicherweise gibt es keine Regeln dafür, wie viele Funktionsaufrufe tief man zurückgeben kann.

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');

Das ist für mich die Schönheit von Pfeilfunktionen. In einer einzigen Zeile können Sie elegant eine Funktion dritter Ordnung erstellen. isKeyEqualToValue ist eine Funktion, die die Funktion isPrice zurückgibt, die die Funktion isCheap zurückgibt.

Sehen Sie, wie einfach es ist, mehrere gefilterte Listen aus dem ursprünglichen restaurants-Array zu erstellen?

Siehe den Pen .filter – returning predicates von Adam Giese (@AdamGiese) auf CodePen.

Prädikate komponieren

Wir können unser Array jetzt nach Burgern oder nach einem günstigen Preis filtern... aber was ist, wenn Sie günstige Burger wollen? Eine Möglichkeit ist, zwei Filter zusammenzuketten.

const cheapBurgers = restaurants.filter(isCheap).filter(isBurger);

Eine andere Möglichkeit ist, die beiden Prädikate zu einem einzigen zu „komponieren“.

const isCheapBurger = restaurant => isCheap(restaurant) && isBurger(restaurant);
const isCheapPizza = restaurant => isCheap(restaurant) && isPizza(restaurant);

Sehen Sie all diesen wiederholten Code. Wir können das definitiv in eine neue Funktion verpacken!

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);

Was ist, wenn Ihnen entweder Pizza oder Burger gefallen?

const either = (predicate1, predicate2) => value =>
  predicate1(value) || predicate2(value);

const isDelicious = either(isBurger, isPizza);
const deliciousFood = restaurants.filter(isDelicious);

Das ist ein Schritt in die richtige Richtung, aber was ist, wenn Sie mehr als zwei Lebensmittel einschließen möchten? Das ist kein sehr skalierbarer Ansatz. Es gibt zwei integrierte Array-Methoden, die hier nützlich sind. .every und .some sind beides Prädikatsmethoden, die ebenfalls Prädikate akzeptieren. .every prüft, ob jedes Element eines Arrays ein Prädikat besteht, während .some prüft, ob irgendein Element eines Arrays ein Prädikat besteht.

const isDelicious = restaurant =>
  [isPizza, isBurger, isBbq].some(predicate => predicate(restaurant));

const isCheapAndDelicious = restaurant =>
  [isDelicious, isCheap].every(predicate => predicate(restaurant));

Und wie immer verpacken wir sie in eine nützliche Abstraktion.

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 und isAny akzeptieren beide ein Array von Prädikaten und geben ein einzelnes Prädikat zurück.

Da all diese Prädikate einfach durch höherwertige Funktionen erstellt werden können, ist es nicht allzu schwierig, diese Prädikate basierend auf Benutzerinteraktionen zu erstellen und anzuwenden. Wenn wir alle gelernten Lektionen berücksichtigen, hier ist ein Beispiel für eine App, die Restaurants durch Anwenden von Filtern basierend auf Button-Klicks durchsucht.

Siehe den Pen .filter – dynamic filters von Adam Giese (@AdamGiese) auf CodePen.

Zusammenfassung

Filter sind ein wesentlicher Bestandteil der JavaScript-Entwicklung. Egal, ob Sie schlechte Daten aus einer API-Antwort aussortieren oder auf Benutzerinteraktionen reagieren, es gibt unzählige Male, an denen Sie eine Teilmenge der Werte eines Arrays wünschen würden. Ich hoffe, dieser Überblick hat Ihnen geholfen, Prädikate zu manipulieren, um lesbareren und wartbareren Code zu schreiben.