A Beginner's Guide to JavaScript's Prototype

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
You can't get very far in JavaScript without dealing with objects. They're pretty foundational to almost everything that we do in the JavaScript programming language. So, what we're going to do in this video is, we are going to look at a few different patterns for instantiating new objects, and in doing that we're going to get a crystal clear understanding of what JavaScript's prototype is. So let's start off pretty slow here. In JavaScript, the most common way to create new objects is with the curly braces, right? Now, if we want to add properties to this object, we can do that using dot notations, this is something you've probably seen thousands of times now. And then if we wanted to add an e-methods on to this object, I'll just paste this in here. We can do that just by adding new properties to the animal object. And then probably eventually what would happen is you would need in your application to create multiple animals. So what you would do is you would take all of this logic, and you will wrap it inside of a function. So we can have a function here called animal, whoa, animal we can have it take in name and an energy level, and then we can paste all of this inside of here. And then now instead of hard coding these, we'll just say animal.name is name and animal.energy is energy. And now we just want to make sure that we return animal down here. So now whenever we want to create a new animal, all we have to do is invoke our animal function, pass it a name as well as an energy level. So here we have two different animals, one called Snoop, one called Leo, and there we go. So, up until this point, this has probably been pretty boring, right? So all we're doing here is we have a function, it creates an object and then eventually just return us that object after it's added some properties to it, nothing too exciting, but can you think of any problems with what we have so far. So right now when we just have two animals, it's not that big of a deal, but imagine our application was...imagine we were building like a Noah's Ark game, or something, that's a really lame example, but that's what I'm gonna go with. So imagine we were building that app, we would have lots of different animals inside of our application. So what we're doing right now is every time we create a new animal, we are recreating in memory each of these specific methods: eat, sleep, and play. What if instead of recreating those every time, we created a new instance? Instead, we put these methods somewhere because these are very generic. They don't need to be specific per instance, right? All of these because we're using to this keyword in here can be shared across all instances because they're not specific to any one animal, they're specific to all animals. So what we can do is let's move all of these methods out to their own object, and then inside of here we can just reference that objects. What that'll look like is what if we do something like this, create an animal, methods, objects, and then inside of here, we are going to have these three methods. We'll need to format this a little bit. We can get rid of the function keyword, we can also get rid of animal, and then we need to add commas between each of these methods. So we have eat, sleep, and then play, so that looks pretty good. And then now inside of animal instead of adding and recreating these methods each time, we invoke animal, what we can do, is we can come in here and say animal.eat is just going to be a reference to animalmethods.eat. And then we'll do the exact same thing for sleep and play. We'll say animal.sleep is a reference to animalmethods.sleep, and then finally animal.play, is just a reference to animalmethods.play. So now we've solved this solution, we only ever create animal methods once in memory. And then any time we create a new animal, what we do, is we give an animal the ability to eat, sleep, and play, but we're just referencing our animal methods object instead of recreating those every time. Now, can you think of any problems or any downsides with what we're doing currently? Well, it's not a huge issue but let's say we came in here and we added like poop, right, animals need to poop that's reasonable. Now, what we'd have to do is we'd have to remember to go into the animal definition, and then add this line right here animal.poop equals animalmethods.poop, right? So instead of doing that, what we want to do, is we want to say, "Hey, we want animal to always be able to reference any of the methods that are located in the animal methods object." To do this, what we'll use is, a feature of JavaScript called object.create, so let's go ahead and take a look real quick at what object.create does. So let's say we had a object here, we'll call it parent, it has a name, age, and a heritage, and then let's say we wanted to create a child object. The important thing here is we want child to be able to have the exact same heritage as the parent. So what we can do is we can say child is going to be the invocation of object.create that's going to return us a brand new object. So child is going to be the object and here's the cool part, whatever we pass in as the first argument to object.create, whenever there is a failed lookup on the child object, the JavaScript engine is then going to delegate to the parent object. So that was a lot of words, let me show you what's happening here. So let's make sure we give child its own name and its own age, so now, if we console.log child.name, we're going to get Ryan. If we log child.age, we're going to get 7. And then if we log child.heritage, what's gonna happen is JavaScript is gonna say, "Hey, does child have a heritage property?" It doesn't, so let's go ahead and look at the parent object to see if the parent object has that property. So if we log child.heritage even though child again doesn't have the heritage property, because we've used object.create passing in parent, the interpreter, the JavaScript engine will delegate that lookup to the parent object and then we will get Irish. So there we go, so what we can do now, is instead of all of these right here where we say animal.eat, animal.sleep, animal.play, when we create the animal object instead of it just being an empty object just as we barely saw with the parent and the child, we can say go ahead and create an object that's going to delegate to animal methods on any failed lookups. So down here now when we run leo.name that's going to give us Leo, because Leo has a name property. But if we try to do…let's just do leo.play, the JavaScript engine is gonna say, "Okay it looks like Leo doesn't have a play property, Leo only has a name and an energy. Let's go ahead and check animal methods to see if play exists on animal methods." It will, and then it'll invoke this function right here. So before our problem was that if we added a method to animal methods, we wanted that to automatically be included in all of the different animals. And now because we are using object.create, no matter what method we add to animal methods, when there is a failed lookup on any animal, the JavaScript engine will then delegate that lookup to animal methods, and then if it finds it, it will invoke the individual method. All right, there are still a few more ways that we can improve this code, and this is where it starts getting cool, as cool as instantiation patterns in JavaScript can get. This seems like such a common pattern that we would run into that there would be a built-in way in the JavaScript language to do this, right? Instead of having to manage our animal methods object which contained all of these shared methods across all of the different animals, it seems like that it would just be a better place to put this, right, something to do with the function itself. And this is where JavaScript's prototype comes into play. So what a prototype is, is it's just a property on a function. So here we have a function, it's not doing anything. You'll notice if we do imAFunction.prototype that gives us back an object with just a constructor property on it. So all a prototype is in JavaScript is it's just a property on an object that points…is it's just a property on a function that points to an object. Again, I'm gonna say this again because so many different resources out there make this way harder than it needs to be. All the prototype is, and if you got this question in a job interview, this is all I want you to say, to say, "Hey what's a prototype in JavaScript?" All you have to say is a prototype is just a property that every function you create in JavaScript has that points to an object. That's all it is. Prototype is a property on a function that points to an object. So what if instead of creating this separate object here which contains all of our methods, what if we just put all of these methods on the functions prototype? Don't overthink that. Prototype is just an object that every function has, so instead of having to manage our animal methods separately, what if we just go ahead and put those on the prototype. So let's see what that would look like. So we know that every function has a prototype, so we could say, animal.prototype.eat is going to be a function. Animal.prototype.sleep is going to be a function, and then animal.prototype.play is going to be a function. So now let's just go ahead and copy these, eat takes in an amount. That looks good. We'll do the exact same thing for sleep which takes in a length. And finally we have play which also takes in a length. Now what that allows us to do, is we can get rid of our animal methods. And then now the only thing we have to change is right here we are saying go ahead and delegate to animal methods. But now we want to say, "Go ahead and delegate to animal.prototype." So just so I can show you that this is working, let's go ahead and paste this into our console. Okay, that looks good, now let's create our instance of animal. So now if we look at Leo, Leo has a name and energy but because of this line right here, when we do leo.play, the JavaScript engine is gonna say, "Okay, does Leo have a play method. It doesn't, so let's look to animal.prototype to see if animal.prototype has a play method." So then it looks to animal.prototype, we have a play method so then it runs that method right there, and we get Leo is playing. So the big takeaway from what we've learned so far is that prototype is just a property that every function in JavaScript has, and as we just barely saw, it allows us to share methods across all instances of a function, and an instance being individual animals like Leo or Snoop. All the functionality is still the same but now instead of having to manage a separate object for all the methods like we had earlier, we can just use another object that comes built-in to the animal function itself, which is animal.prototype. So at this point there is three things that we know. One is how to create a constructor function and this is called a constructor function because it's constructing an object for us. The second thing is how to add methods to the constructor functions prototype, and then the third is how to use object.create to delegate failed lookup to the functions prototype. And all of this is for the purpose of sharing methods across all instances of a particular constructor function. Now, it seems like there should be an easier kind of like baked in solution to handle this. What we're doing here isn't that uncommon, we're just wanting to share methods across all instances of a function, that should be pretty fundamental to any programming language. And as you can probably guess, there is an easier way or a more built-in way of handling what we're doing here. So looking back at our animal constructor function right here, the two most important lines are this line right here and this line right here. Without this line, we obviously wouldn't ever create the object itself, and we wouldn't delegate to the animals prototype on failed lookups. And without this line we would never actually get the object that we created. So in JavaScript, you may have heard of the new keyword before. The way it's used is like this, so when you invoke a function, you put the new keyword in front of it. So now it's as if we are creating a new instance of animal, so now the question becomes what's the difference between just doing something like this, just invoking a function as you normally would, and including the new keyword in front of that function. Here's the cool part. So if we…let's actually go ahead and do this, so I'll say animal with new. If you use the new keyword in front of a function invocation, JavaScript automatically behind the scenes implicitly some would say is going to do this line for you, and it's going to do this line for you. The difference is instead of calling this animal JavaScript is going to call this, this, literally T-H-I-S. So now any properties that we want to add to the object, we use the this object. So again, whenever you use the new keyword in front of a function invocation, behind the scenes, JavaScript is going to create an object called this, which is going to delegate to the animal's prototype. And in this case it's going to be AnimalWithNew, and then it's also going to implicitly return that object that it is creating for you. So we can see this in action, let's go ahead and move all of animals prototype methods up here, and then I'm going to copy all of those and change this to AnimalWithNew. And so now what we'll see when we run all of this, Leo and Snoop, both have a name energy. LeoWithNew and SnoopWithNew, both have a name and energy property. Leo can play, LeoWithNew can also play. So what we've done is by using again the new keyword in front of a function invocation that tells JavaScript to create an object called this, which is going to delegate on failed lookups to the functions prototype, and then it's going to implicitly return it. So everything that we had to do before to get to this stage right here, JavaScript will do all of that for you just by including the new keyword. And so we can see how clean this looks by getting rid of these under the hood comments here. And so there we go, here, let me move this up so we can compare them. This is what you would do if you wanted to not use the new keyword, right, so you'd have to use object.create to have all of the instances delegate to animal.prototype on failed lookups. But if you wanted to use the new keyword, you are fine with that, than what you could do is just add the different properties to this. And then this line as well as this line will automatically happen for you by JavaScript itself. If you're coming from a different programming language like Java, really any programming language, you might be a little frustrated right now. Because what I did was I essentially just created a crappy version of a class, right? A class is really kind of just like a function that returns you an object, and you can create different instances of that class. Well, this is the exact same thing we're doing except we're using a function. Well, what's nice about JavaScript is, it's a living language, meaning, it's always changing. And for the longest time JavaScript didn't have a built-in way to create classes so what you had to do was you had to use this. You had to do something like this, right, but now because as of ES6, which came out I believe in 2015, JavaScript now has a class keyword. So what we can do, let me get rid of our animal one here, so we just have AnimalWithNew, and I'm actually going to change this to just be animal. Then down here, we need to make sure that we call the new keyword, use the new keyword, right? So now if we wanted to recreate this using an ES6 class, we can do that like this. Let's do that over here, so we say class animal, the constructor method is what's going to basically like create that object. It's going to be similar to this function right here. So we can take in a name and energy, and then just as we did before we will say this.name equals name, this.energy equals energy. And then now instead of having to worry about adding properties or adding methods to the prototype, what we can do, is we come in here just do something like this. And if you're coming from another programming language, this is going to feel really comfortable. So we have eat, sleep, and play. And then we can copy this logic into there. So, we have eat, we have sleep, and finally we are going to have play. Play takes in length, sleep also takes in length, and then eat takes in the amount. And then now same thing, in order to create a new instance of our class, we just do as we usually word, and use the new keyword. So we can copy this now and I could show you this working in the browser. So we have Leo, we have Snoop, and just as we had before, Leo can play, Leo can eat, Leo can sleep, Leo can do any of the things that are on the specific class. So you may have the question if this is the new way to create classes in JavaScript, why did we spend so much time going over the old way with prototypes, with this, and with a new keyword? Well, the reason for that is because the new way, this way using the class keyword is primarily just syntactical sugar over the existing way that we saw earlier. So to really fully understand what's happening with the class keyword, you have to first understand what's happening with what this gets compiled down to which is really just function in locations with the new keyword, and then adding shared methods on a functions prototype. So at this point we've covered the fundamentals of JavaScript's prototype. The rest of this video is going to be dedicated to understanding other kinda like good to know topics related to prototype. Let's go ahead and do that now. When you create a new array in JavaScript, you're probably used to doing something like this, right? Well, this way of creating an array is really just syntactical sugar over creating a new instance of array, right, it would be kind of a hassle to have to write out this every time. So JavaScript allows you to create an array like this. Well, one thing that you might have never thought about is how does every instance of an array have all those built-in methods, things like slice, splice, pop, push. Well, now you know it's because all of those methods live on the array's prototype. So we can see this by logging or just by showing array.prototype. So you'll notice here that all of the array methods live on the array's prototype, and then whenever you create a new array, you create a new instance of array. So this is really just like our animal class that we saw earlier, but instead of creating a new instance of animal we are creating a new instance of array. And because we are using the new keyword, friends will now be able to have access to all of these methods that live on the array's prototype. So next, let's look at how we would get the prototype of an object, right? So say we had…let's go back to the code we had earlier, I'm gonna copy and paste all of this. And let's say for whatever reason we wanted to get the prototype of Leo, right, we wanted to get the prototype that Leo will delegate to on failed lookups is a better way to say that. So what we can do is we can use Object.getPrototypeOf, and you pass in the specific instance, and then now prototype, it's going to have all of those methods on it. So if you ever want to for whatever reason, get the prototype that a specific instance delegates to, you can use getPrototypeOf. So just you can see this, prototype should equal animal.prototype and it does. So now what I wanna show is how we can loop over every key inside of Leo and log this to the console. So one way you might approach this is by using a for in loop, so we can say for key, say let key in Leo. Let's just console.log, say key, and then we'll say value. Don't fast forward, I promise we're gonna get to something new here in a second. All right, so what we expect to happen here is we want to log every key inside of Leo, so we expect to see name and then whatever Leo's name is which is obviously Leo, and then energy and then Leo's energy. But you'll notice here we have name, we have energy, and then we have eat, and then we have sleep, and then we have play. What we're doing accidentally is instead of just logging all of the properties on Leo, we are logging all of the properties on Leo as well as all of the methods on the object that Leo delegates to. So why is that happening? A for in loop is going to loop over all of the enumerable properties on both the object itself as well as the prototype it delegates to. Because by default any property you add to the functions prototype is enumerable. Because by default any property you add to the functions prototype is enumerable, we see not only name and energy but we also see all the methods on the prototype eat, sleep, and play. So to fix this, we either need to specify that all of the prototype methods are non-enumerable or/and I think a better way. We need a way to only console.log if the property is on the Leo object itself, and not on the prototype that Leo delegates to on failed lookups. So to do this what we can use, is the hasOwnProperty methods. So what this looks like is we can say leo.hasOwnProperty. If we pass a name that's going to give us true, but if we pass any property that lives on the prototype, like sleep, that's going to give us false. As a quick side note, if we look at the object's property the reason that Leo has access to hasOwnProperty is because hasOwnProperty lives on the objects prototype, right here. Okay, so let's change up our for in loop real quick. So we'll say if leo.hasOwnProperty key then we want to come in here and console.log this. So now instead of logging all of the prototype properties as well, we get just name as well as energy. The next trick I want to show you about is how to check if an object is a specific instance of a class? So we already have our animal class up here, let's create another class, just call it…let's say user so it's just a function for now. So we have an instance of Leo, so we can say Leo instanceof, whatever the class name is, in this case we can use animal, and this should be all lowercase for whatever reason. That's gonna give us true because Leo is an instance of animal. And if we did Leo instanceof user, that'll give us false, because obviously Leo is not an instance of user. All right, so the very last thing I want to talk about is arrow functions. Arrow functions don't have their own this keyword. As a result arrow functions can't be constructor functions, and if you try to invoke an arrow function with a new keyword it'll throw an error. So let's pretend we tried to create our animal constructor function using an arrow function. So if we try to create an instance of animal now using the new keyword, what's gonna happen is we get this error, saying, "Animal is not a constructor." And even more than this because we just demonstrated that we can't use the new keyword on arrow functions, there is no purpose of arrow functions having a prototype property. So if we come in here and try to say animal.prototype, notice we get undefined. So again you can't use a constructor function or you can't use a new keyword on an arrow function. And because of that arrow functions don't have a prototype property.
Info
Channel: uidotdev
Views: 71,344
Rating: undefined out of 5
Keywords: javascript, prototype, functions, prototype chain, javascript tutorial, prototype inheritance, javascript (programming language), javascript programming language for beginners, coding challenge, tyler mcginnis, tylermcginnis, ui, ui.dev, javascript prototype tutorial, javascript protoype chain, javascript prototype, javascript prototype inheritance, js prototype, learn javascript, learn js prototype, prototype inheritance javascript
Id: XskMWBXNbp0
Channel Id: undefined
Length: 28min 23sec (1703 seconds)
Published: Thu Oct 04 2018
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.