Pages

Sunday, August 31, 2014

Javascript Prototypal Inheritance

Everything in JavaScript is an object. There are no classes. So when we talk about inheritance, we are talking about objects directly inheriting from other objects. To achieve inheritance, broadly speaking, we need to store the reference of the parent in the child somewhere. When a property look-up happens in the child, the child is searched first, and if the property is not found there, we move to its parent and search. And so on, until we hit the end of the inheritance chain.

Every object in JavaScript has a secret property to hold such a reference to its parent. This property is denoted as [[prototype]]. So if you want to inherit from some 'parent' object, you simply store the parent's reference in the [[prototype]] property of the child object. This [[prototype]] property is not directly accessible. However, most browsers allow access to it through another property of an object called '__proto__'. I believe IE8 and below does not provide __proto__ too. So anywhere I use __proto__, remember that it is not cross-browser compatible, and I am using it to access the [[prototype]] property of the object.

So now if I want to inherit, I can do:-

var parent = {
  x : 5,
  y : 10
}

var child = {
  y : 15,
  z : 20
}

child.__proto__ = parent;

parent.y // 10
child.y // 15
child.x // 5

So now we have access to all properties of the parent in the child object. When we look for 'x' in the child, it does not find it there and it then moves up the prototype chain. When I say it moves up the chain, I mean that it looks in the object pointed to by the [[prototype]] of the 'child', which is the 'parent' object. And it finds 'x' in the 'parent' object and prints its value.

Does that mean we can test inheritance here using the instanceof operator?

child instanceof parent // false

This is because instanceof expects a constructor function as its right operand. In our case, 'parent' is not a constructor function, it is simply an object.

This brings us to, what are constructor functions?


Before I get into a rant about what they are, lets talk about functions first.

Functions too internally are objects in JavaScript. So like other objects, they too have the secret [[prototype]] property. But apart form this property they have another special property called, umm.. 'prototype'. Yes, I agree we could have had better terminology!

But do not confuse [[prototype]] property with the prototype property. By default the prototype property of a function points to an object that contains a single property by default, namely 'constructor'. This 'constructor' property points to the function itself.

var speak(){
  console.log('Hi reader!');
}

speak.prototype.constructor === speak // true

Coming back to constructor functions. They are simply put, functions. The language makes no distinction between a function and a constructor function. It is how we use them that distinguishes them. If you use a function to create an object using the 'new' keyword, then you can call this function as a constructor function.


function Person(name, age){
  this.name = name;
  this.age = age;
  this.greet = function(){
    console.log('Hello, my name is ' + this.name);
  }
}

var moody = new Person('moody', 25);

Using the 'new' keyword to create an object 'moody', broadly does two things:-

  1. It create a new object pointed to by 'this' and sets all the properties such as 'name', 'age', and 'greet' on it and returns it.
  2. It sets the [[prototype]] property of the returned object to point to the prototype of Person.

Which basically means that moody is inheriting from Person, because its [[prototype]] property now points to something in Person.

moody.__proto__ === Person.prototype // true

I think here we can provide some sort of definition to the 'prototype' property of a function. It is, quite simply put, an object that is assigned to the [[prototype]] of all objects that are created from this function. Now that we know moody was created using a constructor function, we can use instanceof operator to test.

moody instanceof Person // true

So far, this prototype property of a function looks quite useless. It doesn't do much, other than to just attach itself to the [[prototype]] of the inheriting object. Lets perform some experiments and see if we can find a use for it.

var karan = new Person('karan', 26);

Both 'moody' and 'karan' have their own set of 3 properties, namely 'name', 'age' and 'greet'. We can agree here that 'name' and 'age' should be properties specific to moody and karan. However, we don't need 'greet' to be available on individual instances of Person. Also that is what inheritance is all about right? Put common features in the parent, and make child objects refer to them when needed. But in this case if we test:-

karan.greet === moody.greet // false

They should ideally have been same objects. Unfortunately, both moody and karan have their own copy of the 'greet' method. It would make sense to put it in some common basket. This is where 'prototype' property of a function comes in. Lets add it to the prototype instead.

function Person(name, age){
  this.name = name;
  this.age = age;
}

Person.prototype.greet = function(){
  console.log('Hello, my name is ' + this.name);
}

var moody = new Person('moody', 25);
var karan = new Person('karan', 26);

karan.greet === moody.greet // true

Take away from this is, that instance level properties should be a part of the constructor function. The shared properties can be put on the prototype of the constructor.

This was sort of an unstructured writeup on how inheritance works in JavaScript. Or mostly how I interpret it. Do point out if you disagree somewhere or if you would like me to expand on a particular aspect.

No comments:

Post a Comment