Sometimes we learn by building an existing item on our own. Richard Feynman once said:

What I cannot build, I do not understand.

So in essence, You learn a concept more if you can replicate the implementation.

If you understand Nodejs, then you can build a version of your own Nodejs If you understand how v8 works, then you can build your own version of v8. That’s how the world goes.

So in my bid to understand completely JS Array. I decided to build my own version of Array methods.

In this post, I will show you how to implement your own Array.map and .sort methods.

Tip: Use Bit to easily share components between apps and organize them for your team to build JS apps faster. It’s open source and free, give it a try:

React spinner components: choose, try, use

Array.map()

JS is a dynamically typed language in the sense that anything can go for anything (or everything goes for everything, Everything Everything have you seen the movie? :)) and it gives room to hook into built-in methods and rewrite their implementation.

Array is builtin in JS. It has different methods to manipulate Arrays in our code:

Array:

#push

#pop

#filter

#every

#map

#sort

#indexOf

#join

#fill

#keys

...

These methods are contained in Array.prototype . So to override any method in array, we simply do this:

Array.prototype.push = function(valToPush) {

log("Inside our Array.push implementation :)")

// our implementation

} Array.prototype.pop = function() {

log("Inside our Array.pop implementation :)")

// our implementation

} Array.prototype.every = function() {

log("Inside our Array.every implementation :)")

// our implementation

} Array.prototype.forEach = function(cb) {

log("Inside our Array.forEach implementation :)")

// our implementation

} Array.prototype.fill = function(value) {

log("Inside our Array.fill implementation :)")

// our implementation

}

With this our own implementations above are executed whenever we call them in our code:

// ...

const arr = [90, 80, 70, 60] arr.push(50)

// "Inside our Array.push implementation :)" arr.pop()

// "Inside our Array.pop implementation :)" arr.every()

// "Inside our Array.every implementation :)" arr.forEach((i)=>i*i)

// "Inside our Array.forEach implementation :)" arr.fill()

// "Inside our Array.fill implementation :)"

The Array builtin methods will not be executed. In the same way, we can override the Array.map to add our own implementation.

Also, above is how polyfills can be added to our browsers and Node environment if some certain methods are not implemented in our current version.

Array.map

Array.map method loops through an array and modifies the elements according to how the callback function provided wants it.

Array.map takes a callback function, this function contains the implementation of how the elements in an array are to be modified.

Example:

let arr = [2, 3, 4, 5]

arr = arr.map((element)=> element * 2)

console.log(arr) // [4, 6, 8, 10]

You see the function callback accepts and element and multiples it by 2 and return the element. So the Array.map will pass each element in the arr array to the function callback (which multiplies them by 2) and return a new array containing the multiples.

So, now we know how Array.map() works. Let’s implement our own.

First we override the Array.prototype.,ap method:

Array.prototype.map() = function () { }

The method should take a function callback as argument:

Array.prototype.map() = function (cb) { }

Now, how do we access the arrays [...] inside Array? Easy, we use this . this refers to the [...]

Doubt?

const log = console.log Array.prototype.map = function(cb) {

log(this)

} const arr = [90, 80, 70]

arr.map(()=>{}) // pic here

// node es_arr.js

// [90, 80, 70]

Now, we know this refers to the array, our implementation will go like this:

const log = console.log Array.prototype.map = function(cb) {

log("Inside our Array.prototype.map implementation :)")

let new_arr = []

for (let item of this) {

new_arr.push(cb(item))

}

return new_arr

}

We used for-of to loop through the elements in our array this . For each item in the array, we call the callback function with the current item item as the parameter, then we push the returned item to a new array new_arr . In the end, we return the new array new_arr .

//...

var arr = [90, 80, 70]

log(arr.map((i) => i * 2)) log(arr.map((i) => i * i))

log(arr.map((i) => i * i * i))

log(arr.map((i) => i / 2)) $ node es_arr.js

Inside our Array.prototype.map implementation :)

[ 180, 160, 140 ]

Inside our Array.prototype.map implementation :)

[ 8100, 6400, 4900 ]

Inside our Array.prototype.map implementation :)

[ 729000, 512000, 343000 ]

Inside our Array.prototype.map implementation :)

[ 45, 40, 35 ]

Array.sort()

This method is used to sort the elements of an array. To sort elements we have different methods/algorithms, they are:

Insertion sort

Selection sort

Merge sort

Bubble sort

Each of them has its weakness and strong points. For this tutorial, we will be using the Merge sort. Before implementing the Merge sort let’s define our sort method in the Array.prototype:

Array.prototype.sort = function(compareFn) {

log("Inside our Array.sort implementation :)")

}

That’s it.

Now comes the Merge sort implementation:

This an implementation of a merge sort algorithm in JS.

We will modify it to suit our needs.

Array.prototype.sort = function(compareFn) {

log("Inside our Array.sort implementation :)")

return mergeSort(this) // Split the array into halves and merge them recursively

function mergeSort(arr) {

if (arr.length === 1) {

// return once we hit an array with a single item

return arr

} const middle = Math.floor(arr.length / 2) // get the middle item of the array rounded down

const left = arr.slice(0, middle) // items on the left side

const right = arr.slice(middle) // items on the right side return merge(

mergeSort(left),

mergeSort(right)

)

} // compare the arrays item by item and return the concatenated result

function merge(left, right) {

let result = []

let indexLeft = 0

let indexRight = 0 while (indexLeft < left.length && indexRight < right.length) {

//compareFn ? compareFn =()=> left[indexLeft] < right[indexRight] : compareFn

let _left = left[indexLeft]

let _right = right[indexRight]

if (compareFn)

compareFn = composeCompareFn(compareFn(left, right))

compareFn = (l, r) => l < r

if (compareFn(_left, _right)) {

result.push(left[indexLeft])

indexLeft++

} else {

result.push(right[indexRight])

indexRight++

}

} return result.concat(left.slice(indexLeft)).concat(right.slice(indexRight))

} function composeCompareFn(compareResult) {

if (Math.sign(compareResult) == -1)

return false

if (Math.sign(compareResult) == 1)

return true

if (compareResult == 0)

return false

}

}

We edited the code to use a custom compare function if one is provided, if not it uses the < symbol (the default sorting rule) to rank the elements in ascending order.

To read more about merge sort see this article:

Now to run our sort function:

//...

console.log(list.sort()) // without a sort function callback

console.log(list.sort((a, b) => a - b)) //with a sort function callback const str = ["c", "a", "e"]

console.log(str.sort())

Output:

$ node es_arr

Inside our Array.sort implementation :)

[ 1, 2, 2, 3, 3, 3, 5, 6, 7, 8, 10 ]

Inside our Array.sort implementation :)

[ 1, 2, 2, 3, 3, 3, 5, 6, 7, 8, 10 ]

Inside our Array.sort implementation :)

[ 'a', 'c', 'e' ]

For exercise, implement your own sort method using the algorithms:

Selection sort

Insertion sort

Bubble sort

Caution

For your polyfills or override to work, it has to load first before your code. Don’t understand? Let’s say you trust your own implementation more the built-in APIs in JS, you need to load your custom implementation before your main code.

// polyfills.js Array.prototype.map = function(cb) {

// ...

} Array.prototype.sort = function(compareFn) {

// ...

}

If you do this in your project:

<html>

<script>

console.log([2,3,4,5,6].map((i)=>i*2))

</script>

<script src="./polyfills.js"></script>

</html>

Our custom code in polyfills.js will not be executed when [2,3,4,5,6].map((i)=>i*2) is run. Why? because it was loaded after the function executed.

To correct it, we need to load the polyfills.js before the main code is executed.

<html>

<script src="./polyfills.js"></script>

<script>

console.log([2,3,4,5,6].map((i)=>i*2))

</script>

</html>

Good, now our custom code will be called.

Conclusion

You see how easy it is to override builtin APIs in JavaScript. This is due to how JS was structured.

We implemented the sort(…) and map(…) methods in Array. You can write your methods and play around with them, you will learn a lot in the process, trust me :)

In our post in this series, we will be looking at how to implement our won:

push(...)

pop()

forEach(...)

It will be fun. Till then.

If you have any question regarding this or anything I should add, correct or remove, feel free to comment below! I’d be happy to talk 👍 😃