Implementing Methods on Prototypes

I am currently stuck in the challengue

Functional Programming: Implement the filter Method on a Prototype

And I can´t seem to grasp what the challenge is asking. I already pass the Map challengue but I think i didn´t understand what I have done really.

I have tried to go back to the lessons on functional programming but I´m not able to make a connection between previous challengues and this one. I´m totally blind. What other sources or blogs would be helpful in order to understand this? I don´t want to look at solutions yet

—Update:

To be honest, I just don´t understand why executing the the code as it comes, when console.log(new_s), the console just returns an empty array

Do you know what the filter method does?
You need to create a method yourself that does the same thing
Inside the method you can reference the array the method is applied on as this

Which bit is it that you’re stuck on? Because there are two separate things at play here:

  1. Writing a function that takes an array of values and a function, loops through the array, and returns a new array made up of every value where function(value) returns true.
  2. Adding a method to a prototype, which is how JS implements inheritence and sharing of functionality — you have a type of object (like Array), and you can attach functions to it’s prototype, which are then available to all other Array objects you want to use.

The challenge has you creating 1 and making use of it via 2. It’s important to grok this, because it allows you to understand how JS works, but to help we need to know what you’ve tried already and what concept you’re stuck on

So literally Like if I would creating the filter method myself?

Like "function filter() {
do something
}

Yeah I am basically stuck at understanding the number 2.

Although I haven´t done number 1 yet I think I would be able to make it, but I lack the understanding how exactly the last var new_s with its function connects with the function in where I have to write the code. Like I don´t quite understand the “callback” and how im supposed to reference things (I imagine that with “this”, but not sure the difference between “this” and “callback” and which if each one reference exactly what)

1 Like

Almost, but no, you need to create a method that works on the array prototype

Array.prototype.myFilter = function(callback) {
   // do something
}

So that when you apply your method and the already implemented filter method you get the same result

[1,2,3,4].myFilter(e => e%2); // returns [2,4]
[1,2,3,4].filter(e => e%2) // returns [2,4]

Yeah that it was i meant. Well i´ll give it a try then and update how it goes, thanks

In the example I wrote above

[1,2,3,4].myFilter(e => e%2)

[1,2,3,4] is this and the function e => e%2 is func or like in the code already written callback

The code already written you have something like

let new_s = s.myFilter( /* a function here */)

s is the array on which the method is applied, and the array returned is stored inside new_s

So this might be helpful, I typed it in a rush so please ask for clarification on anything.

Say you want to create a new type of object (all things in JS are objects, it’s how it works).

We’ll use a function to do that, and the new keyword, because this is what kinda glues it all together. The function is, by convention, called a constructor. And if I use the new keyword when I call that function, it will create an object out of thin air. Inside that function, this is the object itself (there are wierdnesses surrounding this, but for now, that explanation will do):


function MyStupidArray(length) {
  this.length = length;
}

If I do new MyStupidArray(4), I get an object of the type MyStupidArray with a property length, which I’ve set to 4. this is the object I’ve just created, so this.length is a property called length.

let myArr = new MyStupidArray(4);
// myArr is: { length: 4 }

That isn’t really like an array though! So lets say that whatever it’s length is, it should have that many items:


function MyStupidArray(length) {
  this.length = length;

  for (let i = 0; i < length; i++) {
    this[i] = undefined;
  }
}

Now when I do new MyStupidArray(4), this is what I get:

{ 0: undefined, 1: undefined, 2: undefined, 3: undefined, length: 4 }

That’s sorta like an array. I can do this:

let myArr = new MyStupidArray(2);
// { 0: undefined, 1: undefined, length: 2}
myArr[0] = 10
myArr[1] = 20
// { 0: 10, 1: 20, length: 2}

My array has a number of items, each with an index (in order, starting at 0), and a length which tells me the total number of items. This isn’t very useful at all yet though. It’s very clunky.

This is the point where API design becomes important. What do I need to do when I create the object? How do I modify the object? How do I prevent users of my object doing stupid things? For example, I can do

myArr[475] = "hiya"

But the length will still be 2. Ugh. This is an dreadful implentation, but I’ll plow on.

Every function has a property called prototype. Every object created by calling a function using new gets access to that function’s prototype property. It does a few magic things under the hood, but basically it means you can attach functions to your object, and they will apply to all objects of that type that you create.

(Importantly for this challenge, you can modify the functions attached to a prototype whenever and however you want. I’ll come back to that: it is just a case of redefining what they do)

function MyStupidArray(length = 0) {
  this.length = length;

  for (let i = 0; i < length; i++) {
    this[i] = undefined;
  }
}

MyStupidArray.prototype.push = function (value) {
  // remember `this` is going to be the object you create.
  // So `this` is going to be an instance of `MyStupidArray`

  // Assign the value you want to push to the end of the array
  this[this.length] = value;
  // Increase the length by one
  this.length++;
  // return the new length
 return this.length;
}

So if I do:

let myArr = new MyStupidArray()
// myArr is just { length: 0 }
myArr.push(10)
// myArray is now { 0: 10, length: 1 }
myArr.push(20)
// myArr is now { 0: 10, 1: 20, length: 2 }

Let’s add a pop method:

MyStupidArray.prototype.pop = function () {
  // Pop takes the last value in the array, and "pop"s it off
  // Keep a hold of the last value:
  let poppedValue = this[this.length - 1];
  // Delete it from the object:
  delete this[this.length - 1];
  // Drop the length by one
  this.length--;
  // return the popped value
  return poppedValue;
}

And to use it:

let myArr = new MyStupidArray()
// myArr is just { length: 0 }
myArr.push(10)
// myArray is now { 0: 10, length: 1 }
// returns `1`
myArr.push(20)
// myArr is now { 0: 10, 1: 20, length: 2 }
// returns `2`
myArr.pop()
// myArr is now { 0: 10, length: 1}
// returns `20`

This is still really horrible, but it kinda does useful stuff now. Lets add a method that lets me transform every item in the “array”. What it needs is to run a function on every member in the array, but not change the length, just the values:

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

  // Just return the mutated version of my stupid array,
  // so I can see what happened:
  return this;
}

Let’s give it a go:

let myArr = new MyStupidArray()
// myArr is just { length: 0 }
myArr.push(10)
// myArray is now { 0: 10, length: 1 }
// returns `1`
myArr.push(20)
// myArr is now { 0: 10, 1: 20, length: 2 }
// returns `2`

I want to multiply every value by 10. So if I define a function to do that:

function times10 (number) {
  return number * 10;
}

I can give that function to map and it will run once for every value in the “array”

myArr.map(times10)
// myArr is now { 0: 100, 1: 200, length: 2 }
// returns { 0: 100, 1: 200, length: 2 }

One of the tenets of functional programming is immutability. I don’t want map to mutate the array, I want it to give me a whole new array with the modified values.

MyStupidArray.prototype.map = function(callback) {
  // So I create a new stupid array, then copy the properties from
  // the current stupid array into it:
  let myArrayCopy = Object.assign(new MyStupidArray(), this);

  // Then do the same thing:
  for (let i = 0; i < myArrayCopy.length; i++) {
    myArrayCopy[i] = callback(myArrayCopy[i]);
  }

  // Just return the new version of my stupid array,
  // otherwise all this was for naught:
  return myArrayCopy;
}

This now:

myArr.map(times10)
// myArr is still { 0: 10, 1: 20, length: 2 }
// but this returns { 0: 100, 1: 200, length: 2 }

(NOTE this is why chaining works, for example:

[1,2,3,4,5,6].slice(2) // get a slice of the array from index 2  to the end
             .map(v => v * 10) // multiply all the values by 10
             .filter(v => v > 40) // only keep those > 40

Because slice returns this, and this is an array, the value of myArray.slice() is an array. So I can map that. map returns this, which is an array, so the value of mySlicedArray.map() is an array. So I can filter that. And mySlicedAndMappedArray.filter returns this, which is an array, so I can apply another method to that, and so on and so forth)


Thankfully you don’t have to think about how to implement this for basic stuff like arrays, because JS provides an Array object that does all this stuff out of the box. Close to what I’ve described above (note that you don’t need the new for builtin object types, but that it is using it under-the-hood):

let myNormalArray = Array(2)
// {0: undefined, 1: undefined, length: 2 }
myNormalArray[0] = 10;
myNormalArray[1] = 20;
myNormalArray.push(30);
myNormalArray.map(function (v) { return v * 10 });
// etc etc

Better still, JS has built-in syntactic sugar so it ain’t as clunky. [] is the same as Array() which is the same as new Array(0):

let myNormalArray = [10, 20, 30].map(v => v * 10);

So. For the challenge, you’re being asked to make your own version of the filter function. Remember:

  • Objects of a specific type can have functions attached to the prototype property of the function that creates the object. For arrays, that function is called, unsurprisingly, Array.
  • You can add more prototype functions to Array or write new definitions for the ones already present.
  • Inside your prototype function, this is going to refer to the array that your filter function is going to operate on.
  • Arrays always have a length property, so you can access that inside the function using this.length.
  • You can add more prototype functions to Array, or modify the ones already there.

Note that this is a powerful feature and should be avoided as a rule, as it can very easily break things. For example:


Array.prototype = {
  map() { return "I don't want to map your array." },
  filter() { return "I don't want to filter your array." },
  reduce() { return "I don't want to reduce your array." },
  reverse() { return "I don't want to reverse your array." },
  join() { return "join! join!" },
};

Now nothing that tries to use those methods in your program will work! Shame! This can be an attack vector (less of an issue nowadays as there are protections against it, but still): you could, inside a malicious program, redefine how a prototype method used on an object like Window (used in browser APIs) works and get it to do something nasty like log input or whatever :man_shrugging:

3 Likes

Wow , thanks for the long explanation. So, after reading your explanation of this I think I grasp it a little bit what is asking

MyStupidArray.prototype.push = function (value) {
  // remember `this` is going to be the object you create.
  // So `this` is going to be an instance of `MyStupidArray`

  // Assign the value you want to push to the end of the array
  this[this.length] = value;
  // Increase the length by one
  this.length++;
  // return the new length
 return this.length;
}

I see now the challengue is asking just to create a function that is going to work as filter would !

I have started to working on it and I have came up with this:

Array.prototype.myFilter = function(callback){
  var newArray = [];
  // Add your code below this line
  this.forEach(function(element) {
  newArray.push(element);
});
  // Add your code above this line
  return newArray;

};

Because now I understand what the challengue is asking but honestly have two problems:

  1. I´m not working out how could I write something like would behave as a filter function
  2. I now how to reference the global array s (with this, right?), but I don´t know how to reference item % 2 === 1; (although I don´t know if that is necessarty)

Mainly my problem is now that, inside the forEach function I want to say “push the element that passes the test item % 2 === 1 into newArray” but I don´t have any idea how.

You are already pushing all the elements inside new array. You know how to have a piece of code that execute only under certain conditions, right?
You just need to combine the two

You can reference the callback like any function: callback(function_argument), putting as function_argument whatever you need

1 Like

:slight_smile: So this is actually simpler than you think:

  1. What goes into myFilter is a function that takes a value and returns true or false: it could be anything, but for example:
    function isEven (num) {
      return num % 2 === 0;
    }
    
  2. Whatever that function is, in your code it is assigned to callback.
  3. At the minute, you are just pushing all the values back into the new array
  4. callback(element) will tell you whether you should push or not. If that callback is the function I wrote above, I guess you would have an array of numbers and only want the even ones: if (callback(element)) { is going to do whatever is in the if block if the current number is even.
1 Like

Damn, I just pass it certainly adding if(callback(element))

But my face was like the second image :joy::woozy_face::

Nah, but I just have to review js functions because i think i have forgotten how some thing works at i think that is why still there are somethings that not make 100% sense to me. I mean I understand now that callback references that test, which really it´s the most i was interested in discovering so I´m happy, thanks