DEV Community

Cover image for Implement JavaScript Array Methods From Scratch
Aris Zagakos
Aris Zagakos

Posted on • Updated on

Implement JavaScript Array Methods From Scratch

Table of Contents

  1. Introduction
  2. prototype
  3. this
  4. Array Methods
  5. Resources

Introduction

The JavaScript Array class is a global object that is used in the construction of arrays. Array is a special type of object that is mutable and it is used to store multiple values.

In this article, we will implement our own array methods from scratch. These implementations don't intend to replace the existing methods but to provide a better understanding of how these methods work and their uses.

Methods Description
indexOf() Returns the first index at which a given element can be found in the array, otherwise returns -1.
lastIndexOf() Returns the last index at which a given element can be found in the array, otherwise returns -1.
reverse() Returns the reversed array.
forEach() Executes a provided function once for each array element.
map() Creates a new array with the results of calling a provided function on every element in the calling array.
filter() Creates a new array with all elements that pass the test implemented by the provided function.
reduce() Applies a function against an accumulator and each element in the array to reduce it to a single value.

For a better understanding of Higher Orders Functions and specifically map(), filter() and reduce() methods you can check this article.

Before we start to implement these methods, we will take a quick look on how prototype and this work.

What is prototype?

In JavaScript, every function and object has a property named prototype by default. Prototypes are the mechanism by which JavaScript objects inherit methods and properties with each other. Prototypes are very useful when we want to add new properties to an object which will be shared across all the instances.

function User () {
    this.name = 'George',
    this.age = 23
}

User.prototype.email = 'george@email.com';
User.prototype.userInfo = function () {
    console.log('[User name]: ', this.name, ' [User age]: ', this.age);
}

const user = new User();

console.log(user.email); // george@email.com

user.userInfo(); // [User name]:  George  [User age]:  23

Enter fullscreen mode Exit fullscreen mode

In the example above, we create the function object User that has the properties name and age. Then, we access the User function object with prototype property and we add the property email and the function userInfo() to it.

What is this?

The value of this is determined by the object that currently owns the space that this keyword is in (runtime binding).

function User () {
    this.name = 'George',
    this.age = 23,
    this.printInfo = function() {
        console.log(this);
    }
    this.orders = {
        orderId: '12345',
        printOrderId: function() {
            console.log(this);
        }
    }
}

const user = new User();

user.printInfo(); // User { name: 'George', age: 23, printInfo: [Function], orders: { orderId: '12345', printOrderId: [Function: printOrderId] } }

user.orders.printOrderId(); // { orderId: '12345', printOrderId: [Function: printOrderId] }
Enter fullscreen mode Exit fullscreen mode

In the example above, we use again the function object User and add the object orders to it. The user.printInfo() prints the this value and in this case it contains all the properties of the User function object. The user.orders.printOrderId() prints only the properties of the orders object and that happens because the method printOrderId() is called through the orders object.

Let's implement the Array Methods

In order to implement the methods, we will access the Array object via prototype property and then we will add our new methods. The this keyword inside the methods has the value of the array that is calling the corresponding array method.

Custom indexOf

Array.prototype.customIndexOf = function (value) {
    for (let i = 0; i < this.length; i++) {
        if (this[i] == value)
            return i;        
    }
    return -1;
}

const output = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

console.log(output.customIndexOf(2)); // 1
Enter fullscreen mode Exit fullscreen mode

In the example above, the customIndexOf method takes as a parameter a value and then we iterate the array until we find the corresponding value and return its index.

Custom lastIndexOf

Array.prototype.customLastIndexOf = function (value) {
    for (let i = this.length - 1; i >= 0; i--) {
        if (this[i] == value)
            return i;        
    }
    return -1;
}

const output = [1, 2, 3, 4, 5, 9, 7, 9, 9, 10];

console.log(output.customLastIndexOf(9)); // 8
Enter fullscreen mode Exit fullscreen mode

In the example above, the customLastIndexOf method takes as a parameter a value and then we iterate the array until we find the last corresponding value and return its index.

Custom reverse

Array.prototype.customReverse = function () {
    let left = 0;
    let right = this.length - 1;

    while(left < right) {
        let temp = this[left];
        this[left] = this[right];
        this[right] = temp;
        left++;
        right--;
    }
    return this;
}

const output = [1, 'b', 'abc', { name: 'Jonh' }, 10];

console.log(output.customReverse()); // [10, { name: 'Jonh' }, 'abc', 'b', 1]
Enter fullscreen mode Exit fullscreen mode

In the example above, the customReverse method reverses in place the array and returns it.

Custom forEach

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

const output = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

output.customForEach(elem => {
    console.log(elem);
}); // 1 2 3 4 5 6 7 8 9 10
Enter fullscreen mode Exit fullscreen mode

In the example above, the customForEach method takes as a parameter a callback function and it is applied on every element in the array. Also, the callback function receives additional the index and the array itself in case that will be used.

Custom map

Array.prototype.customMap = function map(callback) {
    const results = [];
    for (let i = 0; i < this.length; i++) {
        results.push(callback(this[i], i, this));
    }
    return results;
}

let output = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

output = output.customMap(elem => {
    return 3*elem;
});

console.log(output); // [ 3, 6, 9, 12, 15, 18, 21, 24, 27, 30]
Enter fullscreen mode Exit fullscreen mode

In the example above, the customMap method takes as a parameter a callback function and for each element in the array we apply the callback function and we return the result in a new array. Again, the callback function receives additional the index and the array itself in case that will be used.

Custom filter

Array.prototype.customFilter = function (callback) {
    const results = [];
    for (let i = 0; i < this.length; i++) {
        if(callback(this[i], i, this))
            results.push(this[i]);
    }
    return results;
}

let output = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

output = output.customFilter((elem) => {
    return elem % 2 === 0;
});

console.log(output); // [ 2, 4, 6, 8, 10 ]
Enter fullscreen mode Exit fullscreen mode

In the example above, the customFilter method takes as a parameter a callback function and for each element in the array we apply the callback function and for the values that pass the callback function we return the result in a new array.

Custom reduce

Array.prototype.customReduce = function (callback, initialValue) {
    let value = initialValue;

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

    return value;
}

const output = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const sum = output.customReduce((acc = 0, elem) => {
    return acc + elem;
});

console.log(sum); // 55
Enter fullscreen mode Exit fullscreen mode

In the example above, the customReduce method takes as parameters a callback function and an accumulator variable and we apply the callback function against the accumulator for each element in the array until to reduce it to a single value.

You can check my github repository here.

Resources

Discussion (6)

Collapse
mindplay profile image
Rasmus Schultz

Extending prototypes isn't generally a good idea - plenty of answers in this SO question covering why:

stackoverflow.com/questions/140341...

There is no particular reason to do so in JavaScript in the first place - given that "methods" are really just inherited properties containing functions, and there is no functional difference. It's purely syntax and the implicit this, which is often better explained and easier to use when given explicitly as a proper named argument.

Really, the only time you should extend prototypes is in polyfills.

Learning how to do it is fine, of course, but should come with the disclaimer that most people shouldn't and don't need to do it. 🙂

Collapse
zagaris profile image
Aris Zagakos Author

Hi Rasmus, i agree with you. Actually, i have a disclaimer and i say that it doesn't intend to replace the existing methods. I used these examples for learning purposes only.

Collapse
716green profile image
Bob Bass

This is a really great article because it touches on three big JavaScript concepts that so many people aren't familiar with.

I'm really impressed with this write-up.

Collapse
zagaris profile image
Aris Zagakos Author

Thank you very much Bob, i appreciate it.

Collapse
shadowruge profile image
izaias

Muito bom, queria se possível uma explicação trazendo um exemplo tipo pegando de um input e transformando em um objeto e depois em um array ou até Um array de objetos.
Muito boa a sua abordagem parabéns.

Collapse
rhodes235 profile image
Sathya

Thank you