Implement map on a Prototype - so confused

I’ve hit a wall in trying to understand prototypes. The code below does not work, nor should it because I don’t really know how to add to the prototype. It’s important for me to understand this, and I will, however I don’t feel I’m any further after researching and reading up on instances, constructors, etc.

When I try to call myMap in the console it throws a ‘not defined’ error. From what I understand, this is the constructor which “takes on” the value of the object (so it would be the value inside the array). Is this correct?

At this point I’m just throwing darts in the dark and super frustrated. If anyone could help me break down what is going on I would be so grateful.

var s = [23, 65, 98, 5];

Array.prototype.myMap = function(){
  for(var i = 0; i < this.length; i++){
    this[i]= this[i];
  }
}
var new_s = s.myMap(function(item){
  return item * 2;
});

Your browser information:

User Agent is: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36.

Link to the challenge:
https://learn.freecodecamp.org/javascript-algorithms-and-data-structures/functional-programming/implement-map-on-a-prototype

6 Likes

this[i]= this[i]
This means no change at all. You want to apply the given function argument to each element. In your myMap, there is no parameter that expects a function.

Furthermore, .map() should not mutate the original array; so, myMap() should operate on the copy of the original array.
Therefore, you can’t use this for manipulation.


This is added part that directly address your sub questions.

Since prototype is just an object, you add to the prototype in the form of obj.prototype.key = val.

You probably called myMap incorrectly to get undefined error.

The value of this for the Array is the whole array. That’s why you can access its element with this[idx].

Thanks so much for the help. @camperextraordinaire So this represents the object (the array in this case), and we need to iterate over this. Check. If that is the case then we would iterate over the length of this, correct?

I’m confused as to what the parameter callback in this instance does. We would need to define callback but here is where I’m having problems. I had initially tried to tackle this lesson using a for loop and iterating over callback ( i had thought this had represented the array we are passing in), while pushing each element to newArray. like so:

Array.prototype.myMap = function(callback){
  var newArray = [];
  for(var i = 0; i < callback.length; i++){
    newArray.push(callback[i]);
}
return newArray;
}

This seems way off to me. I ran into the same ‘not defined’ error when I attempt to call myMap() in the console.

@gunhoo93, how would you add a function to a prototype in the form of a key/value pair?

You can treat the name of a function as the key and the actual function definition as the value, so you do it like this.

object.prototype.fn = function() { //...do something }

This results a form like this

object {
    prototype {
        fn: function() {//...}
    }
}

If you are still confused about the implementation, try implementing the .map() that doesn’t operate on Array. For example, you can implement .map() of this form:
myMap(arr, fn)

Now, your goal is to apply the function to each value in the array, but do so with immutability in mind.

1 Like

Would we iterate over this? I’m so confused and feel like I don’t know what I’m doing.

Hmmmnnn, when I do this and push this[i]. to newArray nothing happens.

Thank you again for all your help. Here is what I have so far.

Array.prototype.myMap = function(callback){
  var newArray = [];
  for(var i = 0; i < this.length; i++){
   newArray.push(this[i]);
}
return newArray;
}

I had also attempted to set newArray.push(this[i]); to equal callback, but to no avail.

I think you are better off learning some JS grammer.

How do you turn this[i] to the output of the callback?

To better illustrate this, you want the final output to be

Copy[ fn(arr[1]), fn(arr[2]), ..., fn(arr[n]) ]
1 Like

Thank you. I could not figure out the solution.

// the global Array
var s = [23, 65, 98, 5];

Array.prototype.myMap = function(callback){
  var newArray = [];
  // Add your code below this line
  for(var i = 0;i < this.length;i++)
  {
  	newArray.push(callback(this[i]));
  }
  // Add your code above this line
  return newArray;

};

var new_s = s.myMap(function(item){
  return item * 2;
});

console.log(new_s);
2 Likes

I do not understand this line of code:

newArray.push(callback(this[i]));

We are pushing the element at i index in this (which represents the array in this case), but I do not understand what callback is doing here.

1 Like

What you wrote is the passing code. So, I’m only gonna clarify your further question.

You don’t know what callback is exactly. All you know is that it is a function that will take a single argument and return something useful.

callback is entirely provided by the client of the function and client trusts the implementer calls the callback at the right time and place.

For example, if Array provides .map() with the documentation saying:
It will create copy of the array and apply a given function to each element in the copy.
Then, it is you who decide what to apply to each element, the function simply do the iteration for you.

This is powerful because often iterating over a data structure is coupled with a specific operation such as this.

function doulbeAll(arr) {
    for (var i = 0; i < arr.length; ++i) {
        arr[i] = arr[i] * 2
    }
}

The operation, double, is tied with the iteration; you can only use this function to double each element of the array. Every time you want to write a similar function, you need to write the same iteration boiler plate.

Now you have a function that decouples these things, which allows you to do things like:

function double(n) {
    return n * 2
}

function triple(n) {
    return n * 3
}

arr.map(double)
arr.map(triple)

Look at how eliminating iteration simplifies the code. The result is that you get a function that can double or triple single value, or extend this behavior to apply to many values without writing a new function.

2 Likes

Since you haven’t marked this question as solved, I’m going to throw in my 2 cents and see if it helps.

Disclaimer:

The following will not always be technically accurate. This is intentional. I’m only trying to give you a mental model to work with. And for that, technical accuracy isn’t always required.


As always, it’s best to clear up some terms. So I’ll briefly go over prototypes just in case you don’t understand them already. Instances, constructors, etc are not necessary to understand prototypes as a mental model.

Prototypes:

In javascript everything except primitives is an object, even arrays.

And you can think of objects in javascript of being just like object literals.

This means you can create an object literal with functions, which we call methods, if you desired.

const obj = {
  sayHi() {
    console.log('Hi!')
  }
}

obj.sayHi() // Hi!

That also means that you can think of the array object as looking like this:

const Array = {
  map() {},
  reduce(){},
  '0': 'index 0 value',
  '1': 'index 1 value'
  length: 2
}

Every time you create an array with [], Array.from(), etc, that array has all those properties attached to it.

If you only had a few array objects this would be fine. But if you plan on creating many then it would needlessly take up memory.

So it’s best that we create a new Array object, but only keep the properties that belong to that specific array.

And those are it’s values. To do that, we place all the shared properties on the prototype.

const Array.prototype = {
  map() {},
  reduce(){},
}

// then

const myArrayOne= [ 0, 1 ]
// same as
const myArrayOne = {
  '0': 0,
  '1': 1,
  length: 2
}

const myArrayTwo = [ 0, 1, 2 ]
// same as
const myArrayTwo = {
  '0': 0,
  '1': 1,
  '2': 2,
  length: 3
}

myArrayOne and myArrayTwo now have all their items, their length prop, as well as the shared prototype methods

myArrayOne.map()
myArrayTwo.map()

myArrayOne[0] // 0
myArrayOne[2] // undefined

myArrayOne.length // 2
myArrayTwo.length // 3

Javascript will first look for the map property in the current object. If it doesn’t find it, it will go up each prototype and check there.

But how do you know which array is the one calling the map method?

This brings us to this

this

this is a complicated term, so the following is a very simple explanation of it.

  • this refers to the object that “owns” the current function scope.

Using that as our definition, if we wanted to add a myMap to the Array protoype

So as you see, your initial instinct was right. You have access to the array and it’s properties inside the function as the keyword this

But right now, this function does not return anything. All you are doing is looping over this

Array.prototype.myMap = function(){
  for(var i = 0; i < this.length; i++){
    this[i]= this[i];
  }
}

So how do we return a value to the caller of the function? We return a callback

Callback

You can think of a callback as just that – calling back to the caller of the function.

With a callback, not only can we return a value, but we let the caller decide what to do with what we send them.

For instance… (make sure to scroll the repl to see all the code)

Now that we know that callback is the function passed in, and the function expects an argument of item, how do we forward that?

We just call the function with it’s expected arguments.

Array.prototype.myMap = function(callback){
  //...

  /* whatever you want to pass back */
 callback(item)
}

And if you want the caller to have access to the index of the current value?

Array.prototype.myMap = function(callback){
  //...

  /* you control what the caller has access to */
 callback(item, index)
}


//...
s.myMap(function(item, index){
  return item * 2;
})

In essence

  • you are calling the function passed in for the caller
  • so you decide what arguments they can use
  • but the caller decides what it returns.

Now to answer your question:

  • You need to call the callback with the arguments the callback expects.

Try saying that 5 times fast. lol

I know this was lengthy, but hopefully I cleared up some of this. If I haven’t or I’ve made it even more confusing, let me know and we’ll get hacking at it.

20 Likes

Wow!! Thank you so much for breaking that down for me! The FCC community is amazing and you definitely helped clear up some misunderstandings I had.

My main trouble was understanding just exactly what the callback function was, however I certainly am clear on it now thanks to everyones help. Pardon the noob question, but the caller of the callback function is myMap, correct? Is it semantically correct to say that myMap calls the callback function, which passes the argument (the element within the array in this case) ? Feel free to correct my grammar here.

You need to call the callback with the arguments the callback expects.

That line helped me immensely.

Again, thank you! More questions? I got em! But I’ll save that for another time… :wink:

4 Likes

Sorry, I completely overlooked defining “caller”. There are 2 ways you can look at it

  • technical

    • the caller is the function that invokes this particular function
    • this means myMap is the called function
      • if myMap is in the global scope, the calling function is null
      • if myMap is called from another function’s scope, then that function is the caller
  • mental model

    • the caller is the actual person/code that invokes that particular function
    • this means anywhere myMap is called from, would be the caller
    • it also allows me to think of the logic as
      • I call myMap
      • myMap calls back to me

I personally only use mental models when working through code logic. It allows me to create little stories about what the code is doing. lol.

Yes, since the callback is invoked inside the scope of myMap. And according to the technical definition, it is correct to say that myMap is the caller of the callback.

Your welcome, and ask away. I try to answer as quickly as I can.

3 Likes

Thanks JM, your explanation is great and very clear =]