Coding Exercise: Build JavaScript Array Methods From Scratch

There's no better way to ensure that you understand how something works than building you're own version from scratch. In this article we are going to do just that by creating our own versions of Map, Reduce, Sort, and Filter from scratch. Here's the Codepen for reference.

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

TLDR

If you can write JavaScript array functions from scratch, you'll have a better understanding of how they work!

The Plan

Although it is not recommend, you can overwrite JavaScript functions pretty easily (we are only doing this for demo purposes!).

JavaScript array functions are part of the Array prototype, just like functions on a class in Java, for example. We can overwrite prototype functions like so. Notice that I'm printing a message to the console to prove that the function has been overwritten when we call Array.prototype.map.

const myCustomMapFunction = () {
    console.log("My Custom Map Function!");
}
Array.prototype.map = myCustomMapFunction;

How JavaScript Array Methods Work

JavaScript Array methods heavily leverage ES6 Arrow Functions. If you're not familiar with ES6 Arrow Functions, I would recommend taking Wes Bos's free JavaScript 30 class.

Let's start with an example. Let's say you want to iterate through an array, increment each element (a number) by one, and return the new array. This is example what the Map function does. In the past, you would need to do several things to accomplish this.

  • initialize a new empty array
  • iterate through each element in th eoriginal 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;

Not so bad, but with the built in Map Array function, you can accomplish this in one fancy line.

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

Each of the Array functions we will cover accept a function as a parameter. They will iterate through each element of the array (just like we did above) 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 (see reduce below) will be returned.

Map

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

Ok, let's actually build something and start with Map since we mentioned it above. Let's start by stubbing out the map function to prove that we can successfully override the original Map function.

const myCustomMapFunction = ( callback ) {
    console.log("My Custom Map Function!");
}
Array.prototype.map = myCustomMapFunction;

const arr = [1,2,3];

arr.map();

If you run this code, you should see the appropriate log in the console. Good start! Now we can add the for loop and print out each element. Since the array itself is what calls the method, we get access to that array by referencing "this".

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

}

Now, we need to perform whatever transformation is required by calling the callback function. When we do, we will pass a couple of things, the current element and the current index.

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

}

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

const myCustomMapFunction = ( callback ) {
    const newArray = [];

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

return newArray;
}

Filter

Filter returns a new array of elements filtered from the original array.

Let's start by stubbing out our filter method.

const myCustomFilterFunction = () {
    console.log("My Custom Filter Function!");
}
Array.prototype.filter = myCustomMapFunction;

Now, let's set up the for loop to iterate through each element.

const myCustomFilterFunction = ( callback ) {
    const newArray = [];

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

}

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

const myCustomFilterFunction = ( callback ) {
    const newArray = [];

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

    }

    return newArray;

}

Sort

Sort returns a sorted array from the original array.

Let's start again by stubbing out out sort function with a for loop.

const myCustomSortFunction = ( callback ) {
    const newArray = [];

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

Now things start to get a bit different. We are going to be using Bubble Sort. Here's the strategy.

  • repeatedly iterate through items in array
  • compare adjacent itmes and swap if they are not in order
  • after iterating through n^2 times, the array is sorted

Disclaimer *- *I'm not focusing on the most efficient way to sort, just the easiest for this demo.

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 we can add that now.

const myCustomSortFunction = ( callback ) {
    const newArray = [];

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

We also don't want to alter the original array. To avoid this, we can create copy the original array into the new array using the Spread Operator.

const myCustomSortFunction = (callback) {
    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 zero, we want to swap the two elements.

const myCustomSortFunction = (callback) {
    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, we make a copy of one, replace the first one, then replace the second one with the copy. When we are finished, we return the newly sorted array.

Array.prototype.sort = function(callback) {
  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 = retVal[j+1];
            retVal[j+1] = retVal[j];
            retVal[j] = temp;
        }
    } 
  }
  //array is sorted
  return newArray;
}

Reduce

Reduce iterates through each element and returns one single value

Reduce does not return a new array like these other functions. It actually "reduces" the elements in an array to one final value, a number, a string, an object, etc. The easiest example for using reduce is you want to sum up all the elements in a number array. Let's start with stubbing the function out like we did previously.

const myCustomReduceFunction = ( callback ) {
    console.log("My Custom Reduce Function!");
}
Array.prototype.reduce = myCustomReduceFunction;

const arr = [1,2,3];

arr.reduce();

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.

We can add our for loop now, and make a call to the callback function. The return value becomes the new accumulator. After the loop ends, we return the accumulator.

const myCustomReduceFunction = ( callback, accumulator ) {

    for (let i = 0; i < this.length; i++) {
        accumulator = callback(accumulator, this[i], index);
    }
    return accumulator;
}

Recap

I'm a big believer in understanding how things work behind the scenes, and this level of understanding is important while learning a new language, framework, etc. If you take the time to dig in and really learn how things work, you'll be much better off!