Originally published in the CSS-Tricks article Level Up Your. Filter Game.
.filter is a built-in iteration method that takes an assertion function that is called on each array member of the iteration, filtering out (that is, keeping) the member if the function returns a true value, and filtering out (false) the member otherwise. Finally,.filter returns a subset of the original array.
There are a lot of concepts to explain in this paragraph! Let’s look at each of them.
-
“Built-in” means that the presentation is part of the language — you don’t need to add any libraries to use this function.
-
An “iteration method” is a function that is used on each array member of the iteration. Other iterative methods include.map and.reduce.
-
An assertion is a function that returns a Boolean value.
-
A “truth value” is a value that results in true when converted to a Boolean value. Almost all values are true except undefined, null, false, 0, NaN, and “” (empty string).
Let’s start with.filter. First we have an array variable that contains a list of 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',},];Copy the code
There’s a lot of information in there, and now I want a hamburger, so let’s filter it out of this array.
const isBurger = ({cuisine}) = > cuisine === 'Burger';
const burgerJoints = restaurants.filter(isBurger);
Copy the code
IsBurger joints is our assert function, and joints are a new array of subsets derived from restaurants. The important thing to note here is that the restaurants array itself does not change when the.filter method is executed.
For Codepen notes, burgerJoints are arrays that pass through.
No assertion
For every assertion, there is a corresponding negative assertion.
Assertions are functions that return Boolean values. Because there are only two possible Boolean values, this means it is easy to “flip” the asserted value.
A few hours later, I’m hungry. I’ve already had a hamburger. Now I want something else, as long as it’s not a hamburger. One option is to write an isNotBurger assertion from scratch.
const isBurger = ({cuisine}) = > cuisine === 'Burger';
const isNotBurger = ({cuisine}) = >cuisine ! = ='Burger';
Copy the code
But it seems silly that the assertions are so similar that we wrote duplicate code and were not DRY enough. The other way is to call the isBurger assertion and simply reverse the result.
const isBurger = ({cuisine}) = > cuisine === 'Burger';
const isNotBurger = restaurant= >! isBurger(restaurant);Copy the code
This is even better! If the definition of a hamburger changes, you only need to change the logic in one place. But what if we need several assertions that we want to negate at the same time? Since this is probably something you do all the time, you can write a more generic negate function.
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);
Copy the code
Now, there might be some questions in your head:
. What is the apply?
MDN:
Apply () calls the function with the given this value and the array (or array-like object) argument arguments.
Arguments?
MDN:
Arguments are local variables provided by all functions except the arrow functions. You can use the arguments object inside a function to refer to the list of arguments passed to the function when it is called.
Why the old function form instead of the new cooler arrow function?
In this case, returning the traditional function function is necessary because the argument object arguments _ _ is only available in traditional functions.
Of course, you can do this (write the return function as an arrow function, and use the residual argument operator to receive arguments).
const negate = predicate= >(... args) => ! predicate(... args)Copy the code
Returns the assertion
As we saw in the negate function, a function can easily return a new function in JavaScript. This is useful for writing an assertion creator. Let’s review the isBurger and isPizza claims.
const isBurger = ({cuisine}) = > cuisine === 'Burger';
const isPizza = ({cuisine}) = > cuisine === 'Pizza';
Copy the code
These two assertions are not negative of each other, but have the same logic of judgment, differing only in the value of the comparison. So we can combine these two functions into an isCuisine function:
const isCuisine = comparision= > ({cuisine}) => cuisine === comparision;
const isBurger = isCuisine('Burger');
const isPizza = isCuisine('Pizza');
Copy the code
This is very good! Now, what if we need to filter prices?
const isPrice = comparision= > ({price}) => price === comparision;
const isCheap = isPrice('Cheap');
const isExpensive = isPrice('Expensive');
Copy the code
Now isCheap and isExpensive are DRY, isPazza and isBurger are DRY — but isPrice and isCuisine have repetitive logic code! Fortunately, we can take it a step further.
const isKeyEqualToValue = key= > value => object= > object[key] === value;
// These can be rewritten
const isCuisine = isKeyEqualToValue('cuisine');
const isPrice = isKeyEqualToValue('price');
// This doesn't need to change
const isBurger = isCuisine('Burger');
const isPizza = isCuisine('Pizza');
const isCheap = isPrice('Cheap');
const isExpensive = isPrice('Expensive');
Copy the code
To me, that’s the beauty of the arrow function. In one line, you can elegantly create a third-order function. IsKeyEqualToValue is a function that returns isPrice, and it is also a function that returns isCheap.
See how easy it is to create multiple filter lists from the original restaurants array.
Combined assertions
Now we can filter out restaurants with burgers or cheap prices, but what if we want to filter out burger restaurants with cheap prices? One option is to put two.filters together.
const cheapBurgers = restaurants.filter(isCheap).filter(isBurger);
Copy the code
Another is to “combine” two assertions into one:
const isCheapBurger = restaurant= > isCheap(restaurant) && isBurger(restaurant);
const isCheapPizza = restaurant= >IsCheap (restaurant) && isPizza (restaurant);Copy the code
Look at all this repetitive code. We can wrap it as a new function!
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);
Copy the code
What if you want pizza or a burger?
const both = (predicate1, predicate2) = > value => (predicate1(value) || predicate2(value);
const isDelicious = either(isBurger, isPizza);
const deliciousFood = restaurants.filter(isDelicious);
Copy the code
It’s a step in the right direction, but what if you have more than two foods you want to include? This is not a scalable method. Two built-in array methods,.every and.some, are useful here, both of which accept assertion functions. .every checks if _ every _ member can pass an assertion, while.some checks if any _ and _ array members can pass an assertion.
const isDelicious = restaurant= > [isPizza, isBurger, isBbq].some(predicate= > predicate(restaurant));
const isCheapAndDelicious = restaurant= > [isDelicious, isCheap].every(predicate= > predicate(restaurant));
Copy the code
And, as always, let’s wrap them up in some useful abstractions.
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]);
Copy the code
Both isEvery and isAny take an array of assertions and return an assertion function.
Because all of these assertions are easily created by higher-order functions, it is not difficult to create and apply them based on user interactions. For all we’ve learned, this is an example of an application that searches for restaurants based on button clicks.
conclusion
Filters are an important part of JavaScript development. There are many times when you need to conditionally obtain a subset of an array, whether to retrieve data from an API response or to respond to a user interaction. I hope this article has helped you understand the.filter function and use assertions to write more readable and maintainable code.
(after)