What’s the deal with Objects and Prototypes?
If you’ve just begun your coding journey with JavaScript you may have used classes in JavaScript. But do you understand what’s going on under the hood? It all goes back to the __proto__ property, prototypes, and the prototype chain. This article will aim to give you a better understanding of objects, prototypes and more in JavaScript.
Objects
Before we can dive into deeper topics we will really need to understand the basics of what an object is.
Objects are one of the types in JavaScript. Just like numbers and strings are types in JavaScript. Objects are used to store complex data with keys and values. The values in objects can store any kind of data type. For example, let’s examine the code snippet below.
The object above is called an object literal since we are creating it ourselves instead of using a function or something else to generate it.
How though, do we access the information inside our object? There are two ways to do this. We can either use dot notation or bracket notation.
Dot notation: person.age // 25
Bracket notation: person[“age”] // 25
Most of the time dot notation is the best option. However, bracket notation is usually used if the value inside the property you’re trying to access isn’t known or available until after the execution of some code.
What about functions on objects? They are called methods. We can access them the same way we access other properties. You can access methods via dot or bracket notation but you’ll most commonly see dot notation when methods are accessed. Also, you’ll need to ensure you put parenthesis after the method if you want the function to execute.
Dot notation: person.whistle() // whistling…
Bracket notation: person[“whistle”]() // whistling…
What happens if we try to access a property that doesn’t exist? Let’s try
console.log(person.eyes) // undefined
Uh oh, undefined means the property we tried to access isn’t defined or doesn’t exist yet. What’s a better option for us to see if a property exists on an object?
We can use a method called “hasOwnProperty”. This method will check of the property we are looking for exists inside the object.
person.hasOwnProperty(“eyes”) // false
Okay, that’s better. Now we don’t get an error and we know if the property is there or not.
But what if we want to add a new property to our object? We could use either dot or bracket notation.
Dot notation: person.eyes = 2
Bracket notation: person[“eyes”] = 2
How about removing a property?
Usually, you won’t be deleting properties from an object very often but there may be cases when you want to. In those cases you can use the “delete” keyword. Again, we can use these in both dot and bracket notation.
Dot notation: delete person.eyes
Bracket notation: delete person[“eyes”]
One thing to note: the “delete” keyword throws a TypeError in strict mode if the property is a non-configurable. Which means that’s it a non-changeable or immutable property.
Now that we have a basic understanding of how objects and properties on objects work, let’s talk about a hidden property on every object.
The Proto property
In JavaScript all objects come with a very special property. It’s called the __proto__ property. What is this property and what does it do?
This special property can hold other properties that aren’t actually in the object itself. What do I mean? Well, to see this in action we’ll create another object. However, we will create it in a different way.
First, we’ll create an object literal. Let’s make it so that all of our people that we create always have certain qualities we know most people have.
Now let’s console.log(person)
What do you expect to see?
Name, age and everything from the human object right? Actually, you’ll only see name and age. However, when you console.log(person.eyes), you’ll get 2.
You may say “how is that possible?? Where is that property?”.
It’s on the __proto__ property. Let’s console.log(person.__proto__). Ah, now we see all the other properties. But how did the object know to pull from the __proto__ property when it didn’t find it on the object itself and not give us undefined?
The prototype chain
How does the object know to pull from __proto__ and not give us undefined? Well, when we try to access a property on an object, JavaScript runs through a series or chain of checks called the Prototype Chain. Let’s take person.eyes as an example.
First, it looks on the object itself for the property eyes. It’s not there as we saw from our console logs. However, JavaScript doesn’t give up, it has other places to look.
Next, it looks at the __proto__ property. It does find it there and then it returns it to us. Pretty neat huh?
Since this is a hidden property how do we add things to the __proto__ property? Well, using person.__proto__ is strongly discouraged. So the best way would be to use the Object.create() method as seen above. This is the cleanest and most readable method.
But what about methods that we used above like “hasOwnProperty”. We didn’t see that on the __proto__ property did we? Where is it?
The Prototype object
Remember the Prototype chain we just discussed? Well, it has other places it can look for something other than the __proto__ property on our created object. When an object is created is gets not only a hidden property you can add things to but also a hidden prototype object with its own __proto__ property. Let’s see this in action.
When we call person.hasOwnProperty(“name”), first JavaScript looks on the object for it. Does it exist on the person object? No.
Next it moves to the __proto__ property the person object. Does it exist there? Nope. It still doesn’t give up. Now it looks on the prototype object of the person object. Is it there? Actually no.
Finally, it looks on the __proto__ property of the hidden prototype object. Is it there? Yes! It is. Then it calls the method and we get the value true back.
Prototypical Inheritance
We can even add functions to the prototype object. For example, let’s take our person object. We probably want to create more than one person so let’s create a function that generates person objects for us. We will also add in all the properties from the human object, since we know we want our person to have that as well.
Now we have a way to create people objects! You may have noticed the keyword “this”. If you would like to know more about how the ‘this’ keyword works, there is a great article on it here.
What if we want to add a method for the thing most people like to do, eat. We can add this to the prototype of the generatePerson function.
person1.eat() // nom, nom, yum!
Now, anytime we create a person it will inherit the function we placed on the prototype of our function we used to create our person object. This is called prototypical inheritance. There is another way to do this via the object.create method. We will see this next.
Putting it together
Let’s put together what we have learned so far to create a function that generates a user for a game we are making. This user will have some baked in properties. We will create it so that we don’t need to add anything to the prototype object, it will be packaged for us.
So what’s happening here?
First we are creating a function called userCreator. This function is creating an object with the properties name and score. Notice something familiar? Yes, it’s our Object.create() function.
We see our userStoreFunctions object full of methods is being passed into that function. So this will bind those methods to the newUser object via the __proto__ property. Here is where we see prototypical inheritance in action again.
Let’s log out user1.
console.log(user1) //{“name”: “John Doe”, “score”: 5, “age”: 25}
Our user just scored a point, Let’s update their score.
user1.increment()
Now if we log out user1 score will be 6.
If you’ve been working with classes in JavaScript some of this looks familiar. Let’s create a class that does all is this and see what’s happening under the hood with what we have learned so far.
Classes
So what exactly is a class? According to MDN “Classes were introduced in ES6 (ES2015) and are primarily syntactical sugar over JavaScript’s existing prototype-based inheritance. The class syntax does not introduce a new object-oriented inheritance model to JavaScript.”
This means that even though classes look like something new, under the hood it’s working the exact same way as we have learned. Let’s take a look at an example.
What’s happening here? Well, we are creating a class called User. This class is creates an object. Name, score, and age are properties on the User object.
Next, we see the our methods. These are the same methods we saw in the userStore object.
Finally, we create this object with the new keyword. Let’s log out user2 and see what we get.
console.log(user2) // {“name”:”John Doe”,”score”: 4,”age”:25}
Looks familiar right? It’s the exact same result we get from the UserCreator function.
let’s increase our score for user2
user2.increment()
Now, score for user2 is 5
So, what’s going on here? We don’t see an Object.create() in our class. What’s happening under the hood?
It turns out the “new” keyword is pretty powerful and does a bunch of stuff for us automatically.
The “new” keyword does a couple things for us automatically:
- Runs the function we defined with our information
- Creates a new object for us and stores it the “this” key
- Returns our object for us
- Creates the __Proto__ link on the new object and points to the prototype property on the function that was used to create it (the function we added the “new” keyword to) which itself is an object you can add functions to. We saw this earlier with our generatePerson function.
Closing thoughts
We’ve learned a lot in this article. Hopefully, this has given you a better understanding of objects, prototypes, and classes. You now understand what’s happening under the hood when you use classes. This will help you resolve bugs much easier because you know how it’s working.
If you liked this article give it a couple of claps. Feel free to follow me for more content and you can check out my other articles here.