Understanding array methods in javascript by building them from scratch

16 May 2021 / 6 min read
Javascript

Javascript arrays are packed with some very handy built-in methods. These methods can seem confusing when you're first starting out with javascript.Some of them of can be confusing even after years of coding. One of the best ways to gain a deeper understanding of these methods is to actually build them from scratch.

That's exactly what we're going to cover today. We're going to cover the most popular Array methods in javascript.

All methods that we're going to cover in here are inherited from Array.prototype. Although it's possible, it is not recommended to override the default methods but instead to add your own like Array.prototype[myMethod]

Foreach

Array.prototype.forEach is a method that iterates through an array and for each item it applies a callback function.

This method doesn't return anything and is generally used as an elegant alternative to the classic for loop, providing a simpler and cleaner way of iterating through an array.

Example

Copy
const data = [1, 2, 3, 4, 5]
data.forEach((value, index) => {
    console.log('current value: ' + value)
    console.log('current index: ' + index)
})

The callback function accepts 3 arguments which are the current current value, current index and the initial array. Note the arguments are optional, that's why we didn't pass a third argument which is usually not used at all.

Now let's go ahead and implement our own forEach!

Copy
Array.prototype.myForEach = function (callbackFn) {
    for (let i = 0; i < this.length; i++) {
        callbackFn(this[i], i, this)
    }
}

Note that this is actually the input array.

So what we're doing is just iterating through the input array and applying the callback function on each iteration.

If you go ahead and replace forEach with myForEach in the example above you should get the same result.

Map

Array.prototype.map is a method that iterates through an array and for each item it applies a callback function. The only difference between forEach and map is that the latter returns a new array, equal in length as the initial one. The elements of the new array will be the elements returned by the callback function on each iteration

Example

Copy
const data = [1, 2, 3, 4, 5]
const newData = data.map((value, index) => {
    return value + index
})
console.log(newData) // [ 1, 3, 5, 7, 9 ]

Let's see how we can implement our own method.

Copy
Array.prototype.myMap = function (callbackFn) {
    const newArray = []
    for (let i = 0; i < this.length; i++) {
        newArray.push(callbackFn(this[i], i, this))
    }
    return newArray
}

As you can see, it looks very similar to the implementation of the forEach method, except that now we also want to store each element in a new array after we apply the callback function. And after we've iterated through each element we just need to return the new array

If you go ahead and replace map with myMap in the example above you should get the same result.

Filter

Array.prototype.filter is a method that iterates through an array and for each item it applies a callback function. The method returns a new array containing elements from the initial array for which the callback function returned true

Example

Copy
const data = [1, 2, 3, 4, 5] \n
const newData = data.filter((value, index) => {
    return value % 2 === 0
}) \n
console.log(newData) // [2, 4]

Let's see how we can implement our own filter!

Copy
Array.prototype.myFilter = function (callbackFn) {
    const newArray = []
    for (let i = 0; i < this.length; i++) {
        const isTrue = callbackFn(this[i], i, this)
        if (isTrue) {
            newArray.push(this[i])
        }
    }
    return newArray
}

We initialize an empty array, just as we did on the map but the difference now is that we don't want to push every single element. We want just the ones that evaluate to true after calling the callback on them

If you go ahead and replace filter with myFilter in the example above you should get the same result.

Reduce

Unlike the methods that we've previously explored, Array.prototype.reduce method also takes in a second argument that is the initialValue (also called accumulator). On each iteration, the reduce function is going to re-assign the accumulator with the result of calling the callback function.

Example

Copy
const data = [1, 2, 3, 4, 5]
const reduced = data.reduce((acc, curr) => acc + curr, 0)
console.log(newData) // 15

Let's see how we can implement our own reduce!

Copy
Array.prototype.myReduce = function (callbackFn, accumulator) {
    for (let i = 0; i < this.length; i++) {
        accumulator = callbackFn(initialValue, this[i], i, this)
    }
    return accumulator
}

So all we're doing is just calling the callback function with the accumulator and all the other arguments that we've seen in the previous methods.

If you go ahead and replace reduce with myReduce in the example above you should get the same result.

Concat

Array.prototype.concat is method that combines two or more arrays together. The arrays are combined into a new one without modifying any of the input arrays.

Example

Copy
const data1 = [1, 2, 3]
const data2 = [4, 5, 6]
const combined = data1.concat(data2)
console.log(combined) // [1, 2, 3, 4, 5, 6]

Note that more than one 1 array can be passed as arguments. The method will just combine them together in the same order they were passed in.

Let's see how we can build our on concat!

Copy
Array.prototype.myConcat = function () {
    const newArray = [...this] \n
    for (let i = 0; i < arguments.length; i++) {
        if (Array.isArray(arguments[i])) {
            for (let j = 0; j < arguments[i].length; j++) {
                newArray.push(arguments[i][j])
            }
        } else {
            newArray.push(arguments[i])
        }
    }
    return newArray
}

A couple of things happen in here. The first thing you might notice is that we didn't specify any parameters. That is because we should be able to pass as many parameters as we wish. The way we can access all arguments that were passed when calling the function, is to use the arguments keyword.

Another weird thing that you might've noticed is the [...this] on line 2. That code just copies the original array into a new one so any changes made won't affect the initial array. This is the equivalent of iterating thorough the initial array and pushing each element into the newArray.

Ok, so we iterate through all the arguments, and for each one of them we want to check if we're dealing with an array. If the user didn't pass an array then we can just push the value into the final array. But if the user did pass an array than we want to iterate through that array and push each element into the final array.

If you go ahead and replace concat with myConcat in the example above you should get the same result.

Join

Array.prototype.join is method combines all array elements into a new string, separated by a string provided as the first argument. The initial array is not modified.

Example

Copy
const data = [1, 2, 3, 4, 5]
const result = data.join('//')
console.log(result) // 1//2//3//4//5

Let's see how we can build our own join method!

Copy
Array.prototype.myJoin = function (separator) {
    let resultString = ''"
    for (let i = 0; i < this.length -  1; i++) {
        resultString += this[i]
        resultString += separator
    }
    resultString += this[this.length - 1]
    return resultString
}

So all we're doing is to iterate through the input array and add each element followed by the separator to the final result string. Notice that we're going just until this[this.length - 1] index. That is because we don't want to include the separator after the last element.

Happy coding!