Table of Contents
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
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] }
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
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
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]
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
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]
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 ]
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
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.
Top comments (6)
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. 🙂
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.
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.
Thank you very much Bob, i appreciate it.
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.
Thank you