In a lecture about npm
modules, I told you that you shouldn’t try to reinvent the wheel. First look for
a ready-made solution, and only if you can’t find anything useful, then get down to business yourself.
But very often, even installing the npm
module will be superfluous, because JavaScript already has many useful
functions for working with data.
— For example?
— For example, one of the most common data types in JS is an array. All arrays already contain all methods (functions) to work with the data inside the array.
— I don’t understand how an array can contain functions. It seemed to me that arrays consist of ordinary values, such as numbers or lines.
— Excellent remark. Let’s start with an example of a simple array of a few numbers.
const arr = [ -1, 2, 4, 0, -5, 7 ];
In the line above, I created an array arr
. Imagine that you need to create a new array from all the elements
of the array arr
whose value is greater than zero. How would you do it?
— I would create an empty positives
array, then iterate over the arr
array with a for
loop and put
every matching array element in positives
using the push
function.
— Great plan. Pay attention to the push
function. This is one of the “built-in” functions of all JS arrays.
Even if there are no elements in the array, you can always add a new one using push
.
Let’s try to write your algorithm in JS.
const arr = [ -1, 2, 4, 0, -5, 7 ];
const positives = [];
for (let i = 0; i < arr.length; i++) {
if (arr[i] > 0) {
positives.push(arr[i]);
}
}
console.log(positives); // [ 2, 4, 7 ]
It turned out well, but it could be better.
Filter array elements
The filter
function will help you filter out the unwanted elements from the array and return
a new array consisting only of suitable items.
To work correctly, the filter
function accepts a parameter called “filtering function” or “filtering predicate”.
This “filtering function” takes a single element of the array and returns true
if the element passes the filter, otherwise, it should return false
.
Let’s rewrite the previous example using the filter
function:
const arr = [ -1, 2, 4, 0, -5, 7 ];
const greaterThanZero = item => item > 0;
const positives = arr.filter(greaterThanZero);
console.log(positives); // [ 2, 4, 7 ]
This is not the shortest option, however. I created the greaterThanZero
function just to emphasize that
the filter
function takes another function as a parameter.
In practice, for simple filters, you will more often see an anonymous filtering function written right inside
the filter
brackets.
const arr = [ -1, 2, 4, 0, -5, 7 ];
const positives = arr.filter(item => item > 0);
console.log(positives); // [ 2, 4, 7 ]
— Can I ask a stupid question?
— Sure.
— What happens if my “filtering function” always returns false
?
— If the function that you use in Array.filter
always returns false
, then you’ll get an empty array as an output.
const arr = [ -1, 2, 4, 0, -5, 7 ];
const positives = arr.filter(item => false);
console.log(positives); // []
console.log(arr); // [ -1, 2, 4, 0, -5, 7 ]
Note that you will get a new empty array, and the original arr
array will not change in any way.
— And if I choose to always return true
, then I will get a copy of the input array?
— Right. But it’s like hammering nails with a microscope, so
I do not recommend using the filter
method for copying arrays.
With filter
, you can also work with an array of objects. For example, you might want to filter all
users over 18 years of age.
const users = [
{name: 'Jake', age: 15},
{name: 'John', age: 16},
{name: 'Jill', age: 25},
{name: 'Bill', age: 82},
];
const grownUps = users.filter(user => user.age > 18);
console.log(grownUps); // [ { name: 'Jill', age: 25 }, { name: 'Bill', age: 82 } ]
Iterate over array elements with forEach
The next standard task is to iterate over all the elements of the array. It can also be solved with the regular
for
loop.
Imagine that you need to display the value of all elements of an array, each on a new line.
const arr = [ -1, 2, 4, 0, -5, 7 ];
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
Using a regular for
loop, you call the console.log
function on each iteration of the loop and pass the current
array element arr[i]
in it.
The forEach
function will allow you to shorten your code a bit. It is similar to the filter
function in a way as
you’ll also need a handler function to use it. This function will be called for each element of the array.
The fundamental difference is that forEach
does not return anything.
const arr = [ -1, 2, 4, 0, -5, 7 ];
arr.forEach(item => console.log(item));
— Now let’s think about what we could make the example above even shorter?
— Hmm, I do not know. It seems to be quite short already.
— Look, the item => console.log(item)
function does nothing with the item
parameter other than passing
it further into console.log
which outputs item
to the screen.
It turns out that we could use the console.log
function without this wrapper from item => ...
and the result
would remain the same.
const arr = [ -1, 2, 4, 0, -5, 7 ];
arr.forEach(console.log);
Note that we don’t put parentheses after console.log
because we don’t call the console.log
function.
We pass it as a parameter to the forEach
loop, where it will be applied to each element of the array.
The call will be controlled by forEach
, not by us.
This is a very important point at which many interns stumble.
Loop through array elements with map
The biggest drawback of the forEach
loop is that it doesn’t return anything useful.
If we want to get an array doubleArr
where each element is equal to the element arr
multiplied by two, then
forEach
won’t help.
To get an array equal in length to the initial array, but with modified elements, use the map
function.
const arr = [ -1, 2, 4, 0, -5, 7 ];
const doubleArr = arr.map(item => item * 2);
console.log(arr); // [ -1, 2, 4, 0, -5, 7 ]
console.log(doubleArr); // [ -2, 4, 8, 0, -10, 14 ]
Note that the array arr
has not changed in any way.
Together with filter
, the map
method is one of the most used array methods in JS.
Both filter
and map
return an array, so you could combine them into chains.
Let’s go back to the user example. The task is to create an array with the names of all users whose age is less than 18 years.
Let’s first apply the filter
function to throw away all the underage users and then use map
to
transform an array of objects to an array of strings.
const users = [
{ name: 'Jake', age: 15 },
{ name: 'John', age: 16 },
{ name: 'Jill', age: 25 },
{ name: 'Bill', age: 82 },
];
const kidsNames = users
.filter(user => user.age < 18)
.map(user => user.name);
console.log(kidsNames); // [ 'Jake', 'John' ]
It will become very easy for you to read such code after you get used to the syntax.
The task of creating a new array from an existing one arises so often that the forEach
loop can safely be called
the most useless method in JavaScript. It’s almost always better to use map
or a regular for
loop instead.
Reduce an array to a single value
The reduce
function is the most complex of the standard JavaScript array functions. With reduce
,
you can compress (reduce) an array to one element.
For the reduce
function to work properly, you’ll need 2 parameters — the reducer function and the initial value.
The reducer function, unlike the functions you will use in map
and filter
, takes two parameters.
The first is the accumulator. The second is the current element of the array.
Let’s see how we can calculate the sum of all elements of an array using reduce
.
const arr = [ -1, 2, 4, 0, -5, 7 ];
const sum = arr.reduce((prev, cur) => prev + cur, 0);
console.log(sum); // 7
To make it a little clearer for you, I will rewrite this example using the for
loop.
const arr = [ -1, 2, 4, 0, -5, 7 ];
let prev = 0; // accumulator
for (let i = 0; i < arr.length; i++) {
prev = prev + arr[i]; // arr[i] is the current element of the array, same as cur in the reduce example
}
const sum = prev;
console.log(sum); // 7
— This is clearer, but it looks like there is at least one extra line.
— It’s good that you pay attention to the extra code. The assignment sum = prev
makes the for
loop example
as similar as possible to the previous one with reduce
.
— Can you elaborate on the “initial value” that we pass as the second parameter?
— When the reduce
function starts, it only has the first element of the array, so you need to add an
initial value to start the calculations. Most often, this is an empty string, null, or an empty object.
Let’s see what happens if you try to reduce
on an array of objects and don’t add an initial value.
Here’s an example of how you could calculate the total salary for the year of all employees of the company.
const employees = [
{ name: 'John Richards', salary: 155000 },
{ name: 'Lenny Mingles', salary: 75000 },
{ name: 'Travis Markham', salary: 102000 },
{ name: 'Bertha Jackson', salary: 47000 },
{ name: 'Vasya Pupkin', salary: 322000 },
];
const salariesSum = employees
.reduce((prev, cur) => prev + cur.salary);
console.log(salariesSum); // [object Object]7500010200047000322000
As a result, a weird strange string [object Object]7500010200047000322000
was displayed in the console,
due to the fact that we do not added the initial value of the accumulator.
Let’s add one console.log
to better understand what prev
is on each iteration:
const salariesSum = employees
.reduce((prev, cur) => {
console.log(prev); // added for debugging
return prev + cur.salary
});
New lines will appear in the console.
{name: 'John Richards', salary: 155000}
[object object]75000
[object object]75000102000
[object object]7500010200047000
[object object]7500010200047000322000
Here’s what happened:
- We did not specify the initial value of the accumulator and therefore the first element was used as the accumulator
the
employees
array. - By default, when trying to add an object to a number, both were converted to a string.
- On the second iteration in
prev
string[object Object]75000
which will stick together with numbers until it ends array. - Eventually we will end up with the string
[object Object]7500010200047000322000
.
— It doesn’t look like the sum of salaries of all employees.
— You are right. Does not look like it. Fortunately, fixing the problem is very easy.
Let’s add the initial value of the accumulator and make sure that the salary is calculated correctly.
const employees = [
{name: 'John Richards', salary: 155000},
{ name: 'Lenny Mingles', salary: 75000 },
{ name: 'Travis Markham', salary: 102000 },
{name: 'Bertha Jackson', salary: 47000},
{ name: 'Vasya Pupkin', salary: 322000 },
];
const salariesSum = employees
.reduce((prev, cur) => prev + cur.salary, 0);
console.log(salariesSum); // 701000
The reduce
function can be combined with map
and filter
, but it is important that reduce
is the last one
in the chain.
Let’s try to calculate the total salary of those employees who earn more than one hundred thousand.
const employees = [
{ name: 'John Richards', salary: 155000 },
{ name: 'Lenny Mingles', salary: 75000 },
{ name: 'Travis Markham', salary: 102000 },
{ name: 'Bertha Jackson', salary: 47000 },
{ name: 'Vasya Pupkin', salary: 322000 },
];
const salariesSum = employees
.filter(employee => employee.salary > 100000)
.reduce((prev, cur) => prev + cur.salary, 0);
console.log(salariesSum); // 579000
Enough theory, it’s time to move on to practice!