While some Javascript users may never have to know about prototypes or the object-oriented nature of the language, those coming from a traditional object-oriented programming language will poke around, and then find the inheritance model very odd. What confuses the matter more is the fact that different JS frameworks have their own convenience helpers for writing class-like code, the result of which is that
- there isn't one standard way to do it, and
- the underlying concepts are not as well known as they should be.
Prototype Inheritance
Prototype Inheritance is actually a very simple concept. Its essence is this:
- An object
a
can inherit from another objectb
.b
is said to bea
's prototype. a
inherits all ofb
's properties, i.e. ifb.x
is 1, thena.x
is also 1 automatically.a
's own properties override those ofb
.
Let's see that in action. Let's say we have a John Smith and a Jane that inherits John.
> var john = {firstName: 'John', lastName: 'Smith'}
> var jane = {firstName: 'Jane'}
jane.__proto__ = john
at this point, we say john
is jane
's prototype, and jane
inherits john
's properties
> jane.lastName
"Smith"
jane
's own property takes precedence though
> jane.firstName
"Jane"
Even if you add a property to john
after the fact, jane
will still inherit it - dynamically
> john.hair = 'brown'
> jane.hair
"brown"
Now, let's say Jane gets married - and therefore a new last name.
> jane.lastName = 'Doe'
Again, it will override john
's lastName
> jane.lastName
"Doe"
but if we delete jane
's lastName
now
> delete jane.lastName
it will revert to being inherited from john
> jane.lastName
"Smith"
Now, john
can inherit from other objects too. There can be any number of inheritances in this chain, which we call the prototype chain. In fact, john
does has a prototype
> john.__proto__
Object { }
The result of john.__proto__
is rendered as Object { }
in the Firebug console, but it represents the object Object.prototype
- the ancestor of all objects.
So, that is prototype inheritance in a nutshell. There! That wasn't so bad, was it?
Well, actually... we can't use __proto__
I've got bad news...
__proto__
isn't supported in IE. In fact, __proto__
isn't in the ECMAScript spec and Mozilla is even thinking about dropping it in an upcoming version of Firefox.
That doesn't mean it doesn't exist though. Even on browsers where __proto__
is inaccessible, it is still there under the covers, and we still have to deal with it, just in a more roundabout way.
Classes and Inheritance
So, Javascript doesn't have classes.
Repeat after me: Javascript doesn't have classes.
Okay, then how do methods and inheritance work?
By using prototypes. Whereas in classic OO languages, methods hang off of classes, in Javascript, methods hang off of the prototype of the object, and, the prototype in question is tied to the constructor of the object.
In Javascript, functions double as constructors. You call a function as a constructor by using the new
operator. For example if we create a Cat
function
> function Cat(name){ // <- just a regular function
this.name = name // `this` refers to the object instance being made
}
a new object Cat.prototype
gets created automatically.
> Cat.prototype
Cat { }
we can create a new instance of Cat
using new
var garfield = new Cat('Garfield') // makes a cat - acts as a constructor
now, Cat.prototype
becomes the prototype of each object that's created via new Cat()
, e.g. garfield
.
> garfield.__proto__ === Cat.prototype
true // see? `Cat.prototype` is now garfield's prototype
Now we can hang a method off of Cat.prototype
and it will be available for garfield
.
> Cat.prototype.greet = function(){
console.log('Meow, I am ' + this.name)
}
> garfield.greet()
"Meow, I am Garfield"
It will be available for other cats too
var felix = new Cat('Felix')
felix.greet()
"Meow, I am Felix"
So, in Javascript, methods live on the object's prototype.
Well actually, you can hang methods off of garfield
itself too; it will override the one on Cat.prototype
> garfield.greet = function(){
console.log("What's new?")
}
> garfield.greet()
"What's new?"
But it won't affect the other cats
> felix.greet()
"Meow, I am Felix"
So, a method can live directly on an object itself, a method can live on an object's prototype. A method can also live on any ancestor of an object, i.e. any part of the prototype chain. This is how inheritance is achieved.
To create a second level prototype chain, we first need to create another constructor. How about Animal
?
> function Animal(){
}
Okay, now we need Cat.prototype
's prototype to be an animal, so that a cat will inherit any of animal's methods as well. To do this, we set Cat.prototype
to an instance of Animal
.
> Cat.prototype = new Animal
One more thing: we also want to tell our new Cat.prototype
that it is, in fact, an instance of Cat
> Cat.prototype.constructor = Cat // letting `Cat.prototype`
// know that it is a cat
This is done mainly for housekeeping purposes, but necessary in general.
Now, since an animal inherits from Animal.prototype
and Cat.prototype
is an animal, all cats also inherit Animal.prototype
indirectly. We can add a method to Animal.prototype
and it will be available to any cat instance.
> Animal.prototype.breed = function(){
console.log('Making a new animal!')
return new this.constructor()
}
> var kitty = garfield.breed()
Making a new animal!
Cool, we just did inheritance!
Conclusion
Prototype inheritance in Javascript is pretty strange and will take some getting used to, but it is simple at heart. As long as you understand the underlying concepts you will be able to navigate OO Javascript code in the wild (both good and bad) with confidence.