This article focuses on improving your fluency in using 4 of the main higher order Array
methods:
forEach
map
filter
reduce
forEach
: Use to perform operations on every element without creating a new array.map
: Use to return a new array of the same length where each element has been processed in the same way.filter
: Use to return a (potentially) smaller array based on a truth test, without modifying elements.reduce
: Use to return a single value based on a reduction of each of the elements in the array.
forEach
expects a callback which will be called for every element in the array. The callback will be passed three arguments: the element, the index of the element, and the entire collection. forEach
always returns undefined
.
Use forEach
when you need to perform an action for each element in the array without creating a new array.
Array.prototype.forEach = function (callback) {
for (let i = 0; i < this.length; i++) {
callback(this[i], i, this);
}
}
function logEveryElement(array) {
array.forEach(element => {
console.log(element);
});
}
let elements = [1, 2, 3 ,4];
logEveryElement(elements); // logs 1, 2, 3, 4
function logEveryOtherElement(array) {
array.forEach((element, index) => {
if ( !(index % 2) ) {
console.log(element);
}
});
}
let elements = [1, 2, 3 ,4];
logEveryOtherElement(elements); // logs 1, 3
function logEveryElementGreaterThanArrayLength(array) {
array.forEach((element, index, collection) => {
if (element > collection.length) {
console.log(element);
}
});
}
let elements = [10, 1, 3, 5];
logEveryElementGreaterThanArrayLength(elements); // logs 10, 5
Notice that in the examples above, we use our examples for their side effects and not to return or modify the passed in array.
map
expects a callback which will be called for every element in the array. The callback will be passed three arguments: the element, the index of the element, and the entire collection. map
creates a new array, and pushes into this array the result of calling callback
on each element. map
then returns this new array.
Use map
when you need a new array the same size as the original, but with each of the elements modified by the same operation.
Array.prototype.map = function(callback) {
let result = [];
this.forEach((element, i, collection) => {
result.push(callback(element, i, collection)); // The value returned from calling `callback`
// is pushed into the `result` array.
});
return result;
}
let nums = [1, 2, 3, 4];
let doubledNums = nums.map(num => num * 2);
You can define functions to pass into map
elsewhere, assuming their function signature follows the signature expected by map
:
function triple(num) {
return num * 3;
}
let nums = [2, 3, 4];
let tripledNums = nums.map(triple);
Be careful not to pass functions as the callback into map that expect a second argument, as map
will pass the index of each element as this second argument. Consider the following example using parseInt
which optionally takes a second argument indicating the radix
of the first argument.
let numStrings = ['1', '2', '3'];
let nums = numStrings.map(parseInt); // calls parseInt('1', 0)
parseInt('2', 1)
parseInt('3', 2)
returns [1, NaN, NaN]
In order to protect prevent this unintended behavior, wrap the call to parseInt
in an anonymous function that expects only a single argument, and then, pass that single argument into parseInt
:
let numStrings = ['1', '2', '3'];
let nums = numStrings.map((num) => parseInt(num)); // calls parseInt('1')
filter
expects a callback that returns true
or false
. filter
returns a new array containing the elements from the original array that return true
when passed into the callback.
Use filter
when you need to take an array and return a (potentially) smaller version of it, without modifying any of the elements.
Array.prototype.filter = function(callback) {
let result = [];
this.forEach((element, i, collection) => {
if (callback(element, i, collection) === true) {
result.push(element);
}
});
return result;
}
let nums = [1, 2, 3, 4, 5, 6, 7];
let oddNums = nums.filter(num => num % 2);
Because filter
and map
both return arrays, they are easily chained:
const double = (num) => num * 2
const greaterThanSix = (num) => num > 6
let nums = [1, 2, 3, 4, 5, 6, 7, 8]
let doubledNumsGreaterThanSix = nums.map(double).filter(greaterThanSix)
let numsGreaterThanSixDoubled = nums.filter(greaterThanSix).map(double)
reduce
creates a single value out of an array. In order to do this, it uses accepts a callback function called an accumulator. The accumulator takes two values, and returns a single value. Before demonstrating reduce
, here are a few example accumulators, notice that they each take two values and return a single value:
const smaller = (a, b) => a < b ? a : b
const longer = (a, b) => a.length > b.length ? a : b
const sum = (a, b) => a + b
const product = (a, b) => a * b
const concatenated = (a, b) => a + b // for strings
const concatenated = (a, b) => [...a, ...b] // for arrays
reduce
calls the passed in accumulator on every element in the array. The first argument of the accumulator is the result of the last call to the accumulator. If it is the first call to the accumulator, the first element of the array is passed in, or, if supplied, a default starting value. The second argument to the accumulator is the current element in the array.
Use reduce
when you need to reduce an array down to a single value.
Array.prototype.reduce = function(accumulator, startingValue) {
if (startingValue === undefined) {
startingValue = this.shift();
}
let reduction = startingValue;
this.forEach((element) => {
reduction = accumulator(reduction, element)
});
return reduction;
}
let nums = [1, 2, 3, 4, 5, 6, 7];
let total = nums.reduce((total, num) => total + num)
let nums = [5, 2, 8, 1];
let largest = nums.reduce((total, num) => num > total ? num : total)
forEach
: Use to perform operations on every element without creating a new array.map
: Use to return a new array of the same length where each element has been processed in the same way.filter
: Use to return a (potentially) smaller array based on a truth test, without modifying elements.reduce
: Use to return a single value based on a reduction of each of the elements in the array.
Log the sum of all the numbers, and number like strings in the following array to the console:
let values = ['letters', 3, '5', 4, ['a', 'b'], '2', false, 8, () => {}];
Being familiar with the uses of the higher order array methods, this problem can be approached as follows:
- Use
parseInt
to convert every element in this array to be either a number orNaN
(map) - Filter for values that are not
NaN
(filter) - Reduce the remaining numbers down to a sum (reduce)
Start by simply writing the helper functions for each of these steps:
Step 1: The native parseInt
function will be used. Care must be taken, however, because parseInt
accepts an optional second argument for radix
, and map
will be passing the element's index as a second argument. Because of this, wrap the call to parseInt
in an outer function:
(val) => parseInt(val)
Step 2:
const isNotNaN = (num) => !isNaN(num)
Step 3:
const sum = (a, b) => a + b
Putting it all together:
function makeSumOfNumberLikes(values) {
return values
.map((val) => parseInt(val))
.filter(num => !isNaN(num))
.reduce((total, num) => total + num);
}
let values = ['letters', 3, '5', 4, ['a', 'b'], '2', false, 8, () => {}];
let sumOfNumberLikes = makeSumOfNumberLikes(values);
console.log(sumOfNumberLikes);