Jacob Ruiz

View Original

Mastering Javascript Fundamentals: Prototypes and Constructors

Get the fundamentals down and the level of everything you do will rise. - Michael Jordan

Continuing on day 4 of posting my notes on this this blog as I learn to master Javascript fundamentals one day at a time.

As stated in my original post, I do 1 hour of video lessons from Watch and Code every day. If you're interested in learning Javascript in a way that goes beyond basic tutorials and gives you a foundational, practical knowledge without relying on frameworks - I'd highly recommend it. If you're reading these posts, please keep in mind that these are just my notes, and I'm not an expert (yet!). If your goal is also to master the fundamentals of Javascript, please head over to Watch and Code and start your journey there!

All screenshots were annotated using Shotty.


Prototypes and constructors

Today we're going to talk about how prototypes work with constructor functions.

Constructor functions are functions that are used to create objects.

Constructor functions don't look any different than a normal function, so we capitalize the name of the function as a hint that this is a constructor function.

Inside of a constructor function, two special things happen:

See this content in the original post

This happens without you having to do anything. It's automatic.

For example, if we have a totally empty function as shown in the Dog function above, those two things are still going to happen.

Let's see how that works.

We're going to create a testDog, and set it equal to Dog() (the empty function from above);

See this content in the original post

But when we use a constructor function, the usage is a little different: we must use the new keyword. This is the only thing that distinguishes constructor functions from other functions when they're being used.

So we put the new keyword in front of the function call.

See this content in the original post

That's it. That's how you use a constructor function.

Our function happens to be blank in order to show that these two things are, in fact, happening automatically as long as you use the new keyword:

  1. this is set to an empty object, {}
  2. this is returned

Let's see what we have with testDog.

We can see that testDog is created by Dog:

And Dog is basically just an empty object.

If we go a step further - since we know the constructor function will set this to an empty object and then return it, we can do something like this:

See this content in the original post

Here, we set this.name to name, and expect a name argument.

Now, when we can use the constructor function to create a new dog whose name is set to 'test dog' like this:

See this content in the original post

In the console, if we type testDog and hit return, we can see that the empty object is still returned:

But we also added a name property:

That's the main idea behind constructors.

But how does all this talk about constructor functions relate back to prototypes?

If we put this constructor function into the console:

See this content in the original post

And then use getPrototypeOf:

See this content in the original post

We see an object that is more or less empty, but it has a constructor property that tells us the function that made the object:

This happened automatically. We didn't set the prototype of myDog. Somehow it got this object as its prototype:

Where did this come from?

All functions have a prototype property.

If we run Dog.prototype, it returns an object:

This object: Every object that is created from the Dog constructor (like myDog or randomDog), will get Dog.prototype as its prototype.

Dog.prototype will be the prototype for every object created from the Dog constructor function.

We can prove that this is true by using getPrototypeOf:

Then we can go one step deeper and get the prototype of Dog.prototype, and we see the familiar default object prototype:

With this new understanding of prototypes and constructors, we can draw a new diagram of what the whole system looks like:

Now, let's fix the redundancy of having the fetch method on both myDog and randomDog.

We're going to move fetch off of myDog and randomDog and put it on Dog.prototype.

The code is very simple. We just need to remove the fetch method from the Dog constructor function, and then in a separate line, set Dog.prototype.fetch to our Dog.prototype object.

Now fetch is shared from Dog.prototype and can be used by our specific instances of dog.

We can do this in the console to confirm that the new fetch works:

If we look at myDog, we can see it only has a name property, and no fetch method:

But if we use getPrototypeOf() to get the prototype of myDog, we see our Dog.prototype object with the fetch method.

The point to stress is that every object we create with the Dog constructor function will get Dog.prototype as its prototype.

One small but important thing to talk about, is that in our chart we've been calling the bottom-most piece the default object prototype, but the more accurate name is Object.prototype:

If we type Object.prototype in the console, we actually get a value. And it's similar to Dog.prototype - it's the same idea.

Dog.prototype is the prototype property of the Dog constructor the same way Object.prototype is the prototype property of the Object constructor.

Object is a constructor function!

That's why it's capitalized.

That means that whenever Javascript creates an object, it's automatically using the Object constructor function behind the scenes, which means that all objects have the Object.prototype prototype.

Closing thoughts:

  • Capitalize constructors
  • Don't forget 'new'
  • Using prototypes makes it easier to distinguish between unique vs. shared properties.
  • Array.prototype.forEach() - Array is a constructor, and all arrays get Array.prototype as their prototype, and Array.prototype has a forEach() method.
  • __proto__ - A quick way to inspect prototypes in the console.

Summary

  • We can use constructor functions to create new objects that have shared properties.
  • An object created by a constructor function has its prototype property set to an object automatically created by the constructor function. For example, Dog.prototype.
  • We can add functionality to Dog.prototype and all instances of dog will have that functionality.
  • Array.prototype.forEach() is an example of this