Reverse Engineering: Array.map

Create My Own JavaScript Array.map

Leo Lin (leocantthinkofaname)
7 min readNov 22, 2023
Photo by charlesdeluvio on Unsplash

Years ago, I applied to a financial company for a frontend developer role. Back to the day, I’ve felt like I’m capable to do most of the tasks that a SAAS product centric frontend developer can do. Not saying I’m fully confident, but I believe a lot of us will eventually come to a point and think…

“Com’on, all I do is creating UI with JavaScript. It’s not that hard.”

The job description looks just like another React frontend developer role. And the interviewer did briefly ask some React related questions, which are not so difficult for me. After the chit-chat session, they told me…

“Although this is a frontend developer role, but we also make plugins and libraries. Which heavily relies on your vanilla JavaScript ability. And we don’t actually care about if you know React or not, because it shouldn’t be a problem if you already have a good understanding of JavaScript.”

Then they started to ask me some questions about how JavaScript works behind the scenes. I can barely answer some of them. At that moment I’m sure I’m visibly struggling.

About half hour in, they decide to put me to the real test. And asked…

“Can you implement a map right now?”

And I can’t. I know how to use it… But I don’t know I can implement my own map. What do you mean “implement a map right now”? It’s just there, why would anyone want to do that? How is it possible to make my own map?

Of course, I failed the interview. But it does show me the next step I can take after building UIs for years. It’s to be the one who does not only know how to utilize the tool, but also knows how the tool actually works. And maybe able to invent a new tool based on the knowledge of programming basics. To see an abstraction of something, and able to pick up the suitable data structure for it. To dissect the abstraction and know what I’m looking for. And implement it with the information I gathered from it.

Anyway…

Speaking of reverse engineering, first we need to know how to use it. So, let’s take a look of the minimal use case of map.

[1, 2, 3, 4, 5].map((val, index) => val + index);
// [1, 3, 5, 7, 9]

Here’s what we can see from this minimal example…

  • map is an Array method (duh). So, to implement our own version, we’ll need to extend Array to be able to use it.
  • map essentially just a loop, it loops through all the element in the Array.
  • map takes a callback function as its argument.
  • The callback function has two arguments, the first one is the element itself and the second one is the element’s index.
  • It processes each element with the callback function.
  • Instead mutate the Array, map will return a new Array. Well, we’re not able to confirm this in the example. But we knew it by the spec…

And our end goal will be making an array method called transform with the exact API and behaviour.

[1, 2, 3, 4, 5].transform((val, index) => val + index);
// [1, 3, 5, 7, 9]

To tackle this question, we’ll need to know two JavaScript’s concept. prototype and the infamous this.

Prototype

ECMAScript does not use classes such as those in C++, Smalltalk, or Java. Instead objects may be created in various ways including via a literal notation or via constructors which create objects and then execute code that initialises all or part of them by assigning initial values to their properties. Each constructor is a function that has a property named “prototype” that is used to implement prototype-based inheritance and shared properties. Objects are created by using constructors in new expressions; for example, new Date(2009,11) creates a new Date object. Invoking a constructor without using new has consequences that depend on the constructor. For example, Date() produces a string representation of the current date and time rather than an object.

Every object created by a constructor has an implicit reference (called the object’s prototype) to the value of its constructor’s “prototype” property. Furthermore, a prototype may have a non-null implicit reference to its prototype, and so on; this is called the prototype chain. When a reference is made to a property in an object, that reference is to the property of that name in the first object in the prototype chain that contains a property of that name. In other words, first the object mentioned directly is examined for such a property; if that object contains the named property, that is the property to which the reference refers; if that object does not contain the named property, the prototype for that object is examined next; and so on.

As the specification says, JavaScript achieve inheritance through prototype. And whenever you try to access a property of any object, JavaScript will try to trace it back to its root until it finds the specific property you are looking for.

const arr = [1, 2, 3, 4, 5];

Let’s say we have an array arr. As you probably heard that almost everything in JavaScript is Object. And since JavaScript is a dynamic language, we can assign whatever property to our arr like so…

arr.transform = () => console.log('hello');
// [1, 2, 3, 4, 5, transform: ƒ]

This is basically how we extend our Object. But this only works for the arr object. What we want is all the Array‘s can access the transform method. Remember that whenever you try to access a property of any object, JavaScript will try to trace it back to its root until it finds the specific property you are looking for? So, instead attach the transform method to an instance, we need to do it with Array‘s prototype.

Array.prototype.transform = () => console.log('hello');

If we try to take a look to the Array‘s prototype now, we should be able to see our new method there.

Custom `transform` method inside the Array object

Now, we can call our transform method on any Array, all thanks to the prototype.

[1, 2, 3, 4, 5].transform()
// hello
[2, 3, 4, 5, 6].transform()
// hello

This

The second part is try to understand how this works.

this in JavaScript acts kind of funky. I highly suggest anyone who want to learn JavaScript itself have at least try to walk through the MDN documentation once. I’m not going to explain it here, since it’s a quite over-saturated topic on the internet.

The value of this is not the object that has the function as an own property, but the object that is used to call the function. You can prove this by calling a method of an object up in the prototype chain.

In the Function Context section, we can find the explanation, which is exactly what we need. We need our transform to access the object that calls it.

We can do a small experiment to prove it by modify our transform again…

Array.prototype.transform = function() {
console.log(this);
}
[1, 2, 3].transform();
// [1, 2, 3]

First, we need to convert the arrow function to a function expression. Because we need the Function Context we just talked about. Arrow function does not bind its own this. It inherits its parent scope. In this case it will point to the global/window object.

Once we are able to figure out these concepts, the actual implementation of our transform function should be the simplest thing in the world.

Implementation of transform method

Array.prototype.transform = function(callback) {
var newArr = [];
var len = this.length;
for(var i = 0; i < len; i++) {
newArr.push(callback(this[i], i));
}
return newArr;
}

[1, 2, 3, 4, 5].map((elm, idx) => elm + idx);
// [1, 3, 5, 7, 9]
[1, 2, 3, 4, 5].transform((elm, idx) => elm + idx);
// [1, 3, 5, 7, 9]

Just like our game plan at beginning. Our transform method takes one callback argument. And the callback function takes two arguments, the element itself and the index. And we push the value processed by the callback function into a new array. And then just return the new array at the end of it.

I intentionally use older syntax like var and for. Because whenever we are doing this kind of monkey patching things, we tend to maximize the compatibility. There’s no obvious reason to create our own transform if map is available in our runtime. This is more likely to be happened when we try to make our own map-like function in Internet Explorer or something which doesn’t have const or forEach etc… So, it’s better to keep it as simple as possible without any fancy new syntax. Although we’ll probably use babel for compatibility reason, but it’s a good addition to let the interviewer know what I’m doing.

With the knowledge above, we can implement all the Array method by ourselves.

For example, we can create a forEach like…

Array.prototype.forEachOne = function(callback) {
var len = this.length;
for(var i = 0; i < len; i++) {
// just do whatever the callback want to do
callback(this[i], i);
}
}

Or filter

Array.prototype.funnel = function(callback) {
var newArr = [];
var len = this.length;
for(var i = 0; i < len; i++) {
// only push the one that returns `true`
if(callback(this[i], i)) newArr.push(this[i]);
}
return newArr;
}

Everything!

The only tricky one is the notorious sort… Which I found it’s actually worth some time to implement a JavaScript flavoured sort. Maybe next time…

--

--

Leo Lin (leocantthinkofaname)
Leo Lin (leocantthinkofaname)

No responses yet