.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
.mapals auch.reducesind 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
trueausgewertet wird, wenn er zu einem booleschen Wert umgewandelt wird. Fast alle Werte sind truthy, mit Ausnahmen von:undefined,null,false,0,NaNoder""(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?
Die Methode
apply()ruft eine Funktion mit einem gegebenenthis-Wert undargumentsauf, die als Array (oder Array-ähnliches Objekt) übergeben werden.
Was sind arguments?
Das Objekt
argumentsist 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 Objektargumentsverweisen.
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.
Ist der Vergleich von Strings nicht Case-sensitiv? Mir scheint, dass ('Burger' === 'burger') false zurückgeben sollte.
Sie haben absolut Recht – ich habe gerade alle korrigiert, damit sie großgeschrieben sind.
Natürlich könnte „negate“ auch so geschrieben werden
const negate = method => (…args) => !method(…args);
Das Gleiche gilt für isEvery() und andere ähnliche Elemente, wobei das Array durch die Argumente impliziert wird
const isEvery = (…predicates) => value => predicates.every(p => p(value));
const isCheapAndDelicious = isEvery(isCheap, isDelicious);
Insbesondere da Sie Destrukturierung verwenden, werden Rest-/Spread-Syntax überall unterstützt, wo dies der Fall ist. (Wenn nicht, wird Ihr Code wahrscheinlich sowieso durch Babel laufen, das ihn dann in ES3/5-Code umwandelt.)
Bezüglich Pfeilfunktionen vs. Konstruktorfunktionen –
Sie können auf Argumente in einer Pfeilfunktion mit der
spread-Syntax zugreifen.Ihre negate-Funktion wird also
Tatsächlich glaube ich, dass ES6 sie vollständig überflüssig gemacht hat.
Sie haben absolut Recht – ich habe einen Hinweis zu diesem Abschnitt hinzugefügt, der auf dieses Beispiel verweist.
Warum werden geschweifte Klammern um die cuisine-Variable verwendet, wenn sie als Parameter der Funktion verwendet wird?
Ich verwende Objekt-Destrukturierung direkt am Parameter.
Zum Beispiel:
ist identisch mit
Das stimmt nicht, Sie destrukturieren doppelt
Außerdem, wie andere schon sagten, verwenden Sie nicht
arguments, wenn Sie...spreadingverwenden können.Guter Fang! Behoben.
Vielen Dank für eine klare und prägnante Einführung in
filterund funktionale Komposition. Ich werde diesen Artikel mit den anderen JavaScript-Entwicklern in meinem Team teilen.