Fresh javascript morning - cleaner loop
July 09, 2019
List transformation
Suppose we have this list of people:
const people = [
{
name: 'Jotaro Kujo',
stand: 'Star Platinum',
age: 17
},
{
name: 'Kakyoin Noriaki',
stand: 'Hierophant',
age: 17
},
{
name: 'Joseph Joestar',
stand: 'Hermit Purple',
age: 69
},
{
name: 'Muhammad Avdol',
stand: 'Magician Red',
age: 30
},
{
name: 'Jean Pierre Polnareff',
stand: 'Silver Chariot',
age: 22
}
]
Now we want to extract only the names of those people.
With imperative for
loop:
const names = []
for (let i = 0; i < people.length; i++) {
names.push(people[i].name)
}
console.log(names)
// ['Jotaro Kujo', 'Kakyoin Noriaki', 'Joseph Joestar', 'Muhammad Avdol', 'Jean Pierre Polnareff']
We can think the process of extracting the names as transforming a list of people into a list of names.
In the example above, our intention is only to get the names of each person, that is the line where we .push()
each person’s name into the names
variable. Instead we ended up expressed several unrelated things in the loop operation.
We can use more clean way to express our intent, using .forEach()
method of an array.
const names = []
people.forEach(function (person) {
names.push(person.name)
})
console.log(names)
// ['Jotaro Kujo', 'Kakyoin Noriaki', 'Joseph Joestar', 'Muhammad Avdol', 'Jean Pierre Polnareff']
This is better than using for
loop, because we only express the operation that we care, names.push(person.name)
.
However, this method have drawback, the function inside forEach
is tied to the names
variable.
Instead, we can have data transformation as a single operation, using .map()
:
const names = people.map(function (person) {
return person.name
})
console.log(names)
// ['Jotaro Kujo', 'Kakyoin Noriaki', 'Joseph Joestar', 'Muhammad Avdol', 'Jean Pierre Polnareff']
We can go further by extracting the callback into a separate function:
function getName(person) {
return person.name
}
const names = people.map(getName)
console.log(names)
// ['Jotaro Kujo', 'Kakyoin Noriaki', 'Joseph Joestar', 'Muhammad Avdol', 'Jean Pierre Polnareff']
Now we have even cleaner way to express our intent, to transform list of people into list of people’s names. With idea of data transformation, working with list will be simpler.
List filtering
Suppose we want to make a new list of people with age under 30.
With for
loop:
const youngsters = []
for (let i = 0; i < people.length; i++) {
if (people[i].age < 30) {
youngsters.push(people[i])
}
}
We can also use .filter()
method for that:
function under30(person) {
return person.age < 30
}
const youngsters = people.filter(under30).map(getName)
console.log(youngsters)
// ['Jotaro Kujo', 'Kakyoin Noriaki', 'Jean Pierre Polnareff']
Finding item
Using list of people in the previous section, suppose we want to find a person in the list by their name.
With for
loop, we can see that the operation is burreid inside the loop. We can define additional function to wrap the operation to find a person by their name, to make it reusable.
function findByName(name, people) {
let result = null
for (let i = 0; i < people.length; i++) {
if (people[i].name === name) {
result = people[i]
break
}
}
return result
}
const jotaro = findByName('Jotaro Kujo', people)
console.log(jotaro.name) // Jotaro Kujo
const notFound = findByName('Not Found', people)
console.log(notFound) // null
In the above function, our primary intention is to compare each person’s name with the given name, but we ended up declaring mostly unrelated stuff.
We can use .find()
method instead:
const jotaro = people.find(function(person) {
// if this person's name equals 'Jotaro Kujo' then this person is who we're looking for
return person.name === 'Jotaro Kujo'
})
console.log(jotaro.name) // Jotaro Kujo
The .find()
method takes a function that returns a boolean
indicating if the item match what we want to find.
We can make our function reusable by make it a separate function:
function hasName(name) {
return function(person) {
return person.name === name
}
}
We can use the function like so:
const jotaro = people.find(hasName('Jotaro Kujo'))
console.log(jotaro.name) // Jotaro Kujo
This is definitely more expressive than previous example that use for
loop, and we can clearly see what is the intention of the program.
Notes
The .map(), .filter(), and .find() are all native method of Array
in javascript.