Tutorial

How To Implement JavaScript Array Methods From Scratch

JavaScript

Introduction

JavaScript includes several functions for working with arrays that go beyond the for loop. You may have used these functions in your own projects and wondered how they work and why you’d use one over another.

A great way to understand how something works is to build your own version from scratch. In this article, you are going to do that by creating your own versions of map, filter, sort, and reduce from scratch. When you’re done, you’ll have a better understanding of these functions and their uses.

By combining ES6 Arrow Functions with JavaScript Array functions, you can write extremely powerful and clean code.

How JavaScript Array Methods Work

Let’s start with an example. Let’s say you want to iterate through an array of numbers, increment each element by one, and return the new array. In the past, you would need to do several things to accomplish this:

  • Initialize a new empty array.
  • Iterate through each element in the original array.
  • Alter that element and put the altered value into the new array.

The code would look like this:

const arr = [1, 2, 3];
const newArray = [];

for (let i = 0; i < arr.length; i++) {
    newArray[i] = arr[i] + 1;
}

return newArray;

But with the built-in map function, you can accomplish this in a single line of code:

return arr.map(element => ++element);

JavaScript Array methods heavily leverage ES6 Arrow Functions.

Each of the Array functions we will cover accept a function as a parameter. They will iterate through each element of the array and call that function to determine what to do with each element. After iterating through each element and calling the callback function, a new array or item will be returned.

Prerequisites

To follow this tutorial locally, you will need an editor (such as Visual Studio Code) and a sandbox environment extension (such as Quokka.js).

To follow the tutorial online, you can use CodePen or CodeSandbox.

Step 1 — Implementing Map

map iterates through each element, transforms it in some way, adds it to a new array, and returns the new array.

Warning: In this article, you’re going to extend JavaScript global objects with custom functions. This is for educational purposes only, as this practice has the potential to introduce side effects in production code.

JavaScript array functions are part of the Array prototype, similar to functions on a class in Java, for example. To override them, you can assign a new function to Array.prototype.

Let’s create a new function and assign it to Array.prototype.mapFromScratch:

const myCustomMapFunction = function(callback) {
    console.log('My Custom Map Function!');
}
Array.prototype.mapFromScratch = myCustomMapFunction;

const arr = [1, 2, 3];

arr.mapFromScratch();

If you run this code, you will see the log message in the console:

Output
My Custom Map Function!

Now, add the for loop and print out each element. Due to the array itself calling the method, you get access to that array by referencing this:

const myCustomMapFunction = function(callback) {
    console.log('My Custom Map Function!');

    // 'this' refers to the array
    for (let i = 0; i < this.length; i++) {
        console.log(this[i]);
    }

}

Now, perform any required transformation by calling the callback function. When you do, you will pass it the current element and the current index:

const myCustomMapFunction = function(callback) {
    console.log('My Custom Map Function!');

    // 'this' refers to the array
    for (let i = 0; i < this.length; i++) {
        const transformedElement = callback([this[i], i);
    }

}

Finally, add the transformed elements to a new array and return that array.

const myCustomMapFunction = function(callback) {
    console.log('My Custom Map Function!');

    const newArray = [];

    // 'this' refers to the array
    for (let i = 0; i < this.length; i++) {
        newArray[i] = callback(this[i], i);
    }

    return newArray;
}

Let’s take a look of your rewritten function in action by testing it with a function that will increment each value in the array:

// arr = [1, 2, 3]
// expected = [2, 3, 4]

console.log(arr.mapFromScratch((element) => ++element));

You will receive the following output:

Output
My Custom Map Function! [2, 3, 4]

In this step you implemented a custom map function. Next, let’s explore implementing a filter function.

Step 2 — Implementing Filter

The filter function returns a new array of elements filtered from the original array.

Let’s create a new function and assign it to Array.prototype.filterFromScratch:

const myCustomFilterFunction = function(callback) {
    console.log('My Custom Filter Function!');
}
Array.prototype.filterFromScratch = myCustomFilterFunction;

const arr = [1, 2, 3];

arr.filterFromScratch();

Now, set up the for loop to iterate through each element:

const myCustomFilterFunction = function(callback) {
    console.log('My Custom Filter Function!');

    const newArray = [];

    for (let i = 0; i < this.length; i++) {
        console.log(this[i]);
    }
}

Inside of the for loop, you need to decided whether or not to add each element to the new array. This is the purpose of the callback function, so you use it to conditionally add each element. If the return value is true, push the element on to the return array:

const myCustomFilterFunction = function(callback) {
    console.log('My Custom Filter Function!');

    const newArray = [];

    for (let i = 0; i < this.length; i++) {
        if (callback(this[i])) {
            newArray.push(this[i]);
        }
    }

    return newArray;
}

Let’s take a look at your rewritten function in action by testing it with a function that will display values that are greater than 1:

// arr = [1, 2, 3]
// expected = [2, 3]

console.log(arr.filterFromScratch((element) =>  element > 1));

You will receive the following output:

Output
My Custom Filter Function! [2, 3]

With that, you have implemented a custom filter function. Next you will work with the sort function.

Step 3 — Implementing Sort

The sort function returns a sorted array from the original array.

Let’s create a new function and assign it to Array.prototype.sortFromScratch:

const myCustomSortFunction = function(callback) {
    console.log('My Custom Sort Function!');
}
Array.prototype.sortFromScratch = myCustomSortFunction;

const arr = [3, 2, 1];

arr.sortFromScratch();

We are going to be using Bubble Sort for this sort implementation. Here’s the approach we will take:

  • Repeatedly iterate through items in array.
  • Compare adjacent items and swap if they are not in order.
  • After iterating through the array enough times to make each comparison, the array is sorted.

With Bubble Sort, you have to iterate through the array fully once for each element in the array. This calls for a nested for loop where the inner loop iterates through stopping one short of the final element, so let’s add that now.

Note: This is intended for educational purposes and isn’t an efficient method for sorting.

const myCustomSortFunction = function(callback) {
    console.log('My Custom Sort Function!');

    const newArray = [];

    for (let i = 0; i < newArray.length; i++) {
        for (let j = 0; j < newArray.length - 1; j++) { 
        }
    }
}

You also don’t want to alter the original array. To avoid this, you can copy the original array into the new array using the Spread Operator.

const myCustomSortFunction = function(callback) {
    console.log('My Custom Sort Function!');

    const newArray = [...this];

    for (let i = 0; i < newArray.length; i++) {
        for (let j = 0; j < newArray.length - 1; j++) { 
        }
    }
}

The callback function takes two parameters, the current element and the next element, and will return whether or not they are in order. In our case, if the callback function returns a number greater than 0, we want to swap the two elements.

const myCustomSortFunction = function(callback) {
    console.log('My Custom Sort Function!');

    const newArray = [...this];

    for (let i = 0; i < newArray.length; i++) {
        for (let j = 0; j < newArray.length - 1; j++) {
            if (callback(newArray[j], newArray[j + 1]) > 0) {
                // swap the elements
            }
        }
    }
}

To swap the elements, you make a copy of one, replace the first one, and then replace the second one with the copy. When finished, it returns the newly sorted array.

const myCustomSortFunction = function(callback) {
    console.log('My Custom Sort Function!');

    const newArray = [...this]; 

    for (let i = 0; i < newArray.length; i++){
        for (let j = 0; j < newArray.length - 1; j++) {
            if (callback(newArray[j], newArray[j + 1]) > 0) {
                const temp = newArray[j + 1];
                newArray[j + 1] = newArray[j];
                newArray[j] = temp;
            }
       }
    }

    // array is sorted
    return newArray;
}

Let’s take a look of your rewritten function in action by testing it with a function that quicksorts from low to high:

// arr = [3, 2, 1]
// expected = [1, 2, 3]

console.log(arr.sortFromScratch((current, next) => current - next));

You will receive the following output:

Output
My Custom Sort Function! [1, 2, 3]

Now that you’ve created a custom sort function, you’re ready to move on to implementing a reduce function.

Step 4 — Implementing Reduce

The reduce function iterates through each element and returns one single value.

reduce does not return a new array like the other functions. It actually “reduces” the elements in an array to one final value: a number, a string, or an object. One of the most common reasons for using reduce is for when you want to sum up all the elements in an array of numbers.

Let’s create a new function and assign it to Array.prototype.reduceFromScratch:

const myCustomReduceFunction = function(callback) {
    console.log('My Custom Reduce Function!');
}
Array.prototype.reduceFromScratch = myCustomReduceFunction;

const arr = [1, 2, 3];

arr.reduceFromScratch();

For reduce to return one final value, it needs a starting value to work with. The callback function that the user passes along will determine how to update this accumulator based on each element of the array and return it at the end. The callback function must return the updated accumulator.

Add your for loop now, and make a call to the callback function. The return value becomes the new accumulator. After the loop ends, you return the accumulator.

const myCustomReduceFunction = function(callback, accumulator) {
    console.log('My Custom Reduce Function!');
    for (let i = 0; i < this.length; i++) {
        accumulator = callback(accumulator, this[i]);
    }
    return accumulator;
}

Let’s take a look of your rewritten function in action by testing it with a function that sums up the contents of an array:

// arr = [1, 2, 3]
// expected = 6

console.log(arr.reduceFromScratch((accumulator, element) => accumulator + element, 0));
Output
My Custom Reduce Function! 6

Conclusion

JavaScript’s array functions are extremely useful. In this tutorial, you reimplemented the array functions to get a better understanding of how they work so you can use them more effectively.

0 Comments

Creative Commons License