Skip to content

Instantly share code, notes, and snippets.

@itsgreggreg
Last active May 26, 2016 20:12
Show Gist options
  • Save itsgreggreg/3316ea64c13ec6f310414198828b0dca to your computer and use it in GitHub Desktop.
Save itsgreggreg/3316ea64c13ec6f310414198828b0dca to your computer and use it in GitHub Desktop.
15 minutes to Prototypal Inheritance

Prototypal Inheritance

Goals

By the end of this lesson students should be able to:

  • Demonstrate a use case that explains prototypal inheritance
  • Demonstrate what kind of flexibility prototypal inheritance gives programmers

Summary

In this lesson we are going to learn about a type of inheritance called Prototypal Inheritance. We are going to see how with prototypes we can share functionality between related objects. We are going to see how with Object.create we can easily set up prototypes and how we can extend prototypes to easily add functionality.

Keywords

  • Inheritance
  • Prototype
  • Prototypal Inheritance
  • Prototype Chain

Functions

  • Object.create()
  • Object.getPrototypeOf()

Basic Objects

In javascript we can create Objects to group together related data and functions. For example we might have a Steve object that looks like this:

var Steve = {
  firstName: "Steve",
  lastName: "Jones",
  fullName: function(){
    return this.firstName + " " + this.lastName;
  }
 }

But creating objects like this would soon be cumbersome if we had a lot of different people. We'd have to write that fullName function over and over again for each person which would not only be error prone but super boring. Rather than create a unique set of properties for every person, we can create an abstract object that describes a person in general:

var Person = {
  firstName: "",
  lastName: "",
  fullName: function(){
    return this.firstName + " " + this.lastName;
  }
 }

Notice how the firstName and lastName are now empty strings while the fullName function stays exactly the same as it was before. Now to create a Steve object all we have to do is create a new object based on our Person object and then set the first and last name. We use the function Object.create() to do just that.

var Steve = Object.create(Person);
Steve.firstName = "Steve";
Steve.lastName = "Jones";

And now if we call Steve.fullName() It'll just work without us having to re-write it. This isn't super useful by itself but if we want to create hundreds or thousands of users we can follow this same pattern and never have to worry about re-writing the fullName function. Let's create one more person:

var Molly = Object.create(Person);
Molly.firstName = "Molly";
Molly.lastName = "Heath";

And now Molly.fullName() and Steve.fullName() both work as we'd expect.

Prototypal Inheritance

In computer programming, we call this kind of code reuse Prototypal Inheritance. The Steve and Molly objects can be described as inheriting from the Person object. When we used Object.create(Person), it set up Molly and Steve to inherit from Person. Practically, this means that any functions or properties we defined on Person are available to anything that inherits from it. So even thought we didn't specify a fullName function on Steve or Molly we can access the function as though we had.

When we then set a property directly on Steve like firstName and lastName, it creates those properties directly on Steve and doesn't modify Person. This is what allows us to create many different objects that inherit from Person each with their own unique first and last names but sharing the same fullName function. If we check person we can see that it remains unchanged from when we originally defined it.

> Person
[object Object] {
  firstName: "",
  fullName: function (){
    return this.firstName + " " + this.lastName;
  },
  lastName: ""
}

Prototypes

In javaScript, every object, like our Steve object, has what's called a prototype. An object's prototype is the place that it inherits code from. With this information it's pretty easy to reason that Person is Steve and Mollys prototype. This is because, Person is where Steve inherits code from. We can check what Steve and Mollys prototypes are by passing them to the function Object.getPrototypeOf().

> Object.getPrototypeOf(Steve) === Person;
true
> Object.getPrototypeOf(Molly) === Person;
true

Extending Prototypes

With Prototypal Inheritance it is super easy to extend prototypes to add additional functionality. Steve, Molly, and Person are just regular old JavaScript objects in every sense. And since they are regular old JavaScript objects we can add and remove properties just like we would any other JavaScript object. Let's say we now wanted to give every Object that inherits from Person a default location of "Denver". So we want to be able to say Steve.location and Molly.location and have them return "Denver" by default. Since Steve and every other Object that inherits form Person simply checks itself for a location property, and if it can't find one, will check it's prototype for a location property. We simply have to specify the location property on Person and it will immediately be available to Steve, Molly and any other object that inherits from Person.

Person.location = "Denver";

Now both, Steve.location and Molly.location will be "Denver".

> Steve.location;
"Denver"
> Molly.location;
"Denver"

We can easily change Mollys location while not affecting Steves

Molly.location = "New York";
> Molly.location
"New York"
> Steve.location
"Denver"
> Person.location
"Denver"

Now Molly has a location of "New York" while Steve still has the default location of "Denver". Another way to think of this, is that the Molly Object has it's own "location" property, so when we call Molly.location we get it directly from the Molly Object. The Steve Object on the other hand doesn't have a location property directly on it. So when we call Steve.location and the property isn't found, JavaScript will check Steves prototype, and since Steve's prototype is Person and Person has a location property, JavaScript will return Person.location when we call Steve.location. This checking of the the direct object and then the prototype the essence of Prototypal Inheritance.

##Dopplegangers Say we wanted to create an evil twin of Steve that is the same in every way, except he's evil. The first thing we can do is use Object.create passing it in Steve to create an Object with Steve as it's prototype.

var evilSteve = Object.create(Steve);

All Object.create does is take an object as it's parameter and set that object as the prototype of a new empty object, and then return that new object. So evilSteve right now is just an empty object, with Steve as it's prototype.

> Object.getPrototypeOf(evilSteve) === Steve
true

We want to override the fullName function so we can tell that evilSteve is actually evil. We want to use the old fullName function but we just want to prepend the word "Evil" to it's return value. To do this, we can simply set a fullName property on evilSteve to be a new function that calls the fullName function of evilSteves prototype.

evilSteve.fullName = function(){
  return "Evil " + Object.getPrototypeOf(this).fullName()
}

Now if we call evilSteve.fullName() we'll get "Evil Steve Jones" and if we call Steve.fullName() we'll still get regular old "Steve Jones".

Since evilSteve inherits from Steve and Steve inherits from Person, we can say that evilSteve also inherits from Person. That means that evilSteve also has a default location of "Denver".

> evilSteve.location
"Denver"

Another way of saying this same thing, is that evilSteve has Steve as its prototype and Steve has Person as its prototype. This flow of prototypes from evilSteve through to Person is called the Prototype Chain. Every single object in Javascript has a Prototype Chain and every prototype chain ends in null. So if you have an object like Molly or evilSteve or even an empty object {}, you can pass it to Object.getPrototypeOf() and see what it's prototype is. Then you can send the return value to Object.getPrototypeOf() and continue up the prototype chain until you eventually hit null.

> var proto = Object.getPrototypeOf({});
> proto === Object.prototype;
true
> Object.getPrototypeOf(proto) === null
true

Difference from Ruby

JavaScript has no real notion of classes or instances like in Ruby. In javaScript there are only Objects and every Object has exactly one direct prototype. Prototypes themselves are always also just Objects and so they too have a prototype. In ruby we talk about creating instances of a class and of classes extending other classes. In JavaScript we are simply creating objects, the inheritance comes from the prototype chain. Any Object can have any other Object as it's prototype. Prototypal and Classical inheritance are very different ways of achieving a similar goal of code reuse.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment