Thursday, May 7, 2009

Prototypal Inheritance vs. Classical Inheritance

While it is possible to emulate classical inheritance like Java or C#, it is clear that we do prototypal inheritance by taking advantages of prototype lookup chains. Each has its own advantages and disadvantages. Here I quoted from JavaScript Design Patterns:

The classical approach to creating an object is to (a) define the structure of the object, using
a class declaration, and (b) instantiate that class to create a new object. Objects created in this
manner have their own copies of all instance attributes, plus a link to the single copy of each
of the instance methods.

In prototypal inheritance, instead of defining the structure through a class, you simply
create an object. This object then gets reused by new objects, thanks to the way that prototype
chain lookups work. It is called the prototype object because it provides a prototype for what the other objects should look like.

Instead of using a constructor function named Person to define the class structure, Person
is now an object literal. It is the prototype object for any other Person-like objects that you want to create. Define all attributes and methods you want these objects to have, and give them
default values.


/* Person Prototype Object. */
var Person = {
name: 'default name',
getName: function() {
return this.name;
}
};

var reader = clone(Person);
alert(reader.getName()); // This will output 'default name'.
reader.name = 'John Smith';
alert(reader.getName()); // This will now output 'John Smith'.


To create a new Person-like object, use the clone function. This provides an empty object with
the prototype attribute set to the prototype object. This means that if any method or attribute
lookup on this object fails, that lookup will instead look to the prototype object.
To create Author, you make a clone.


/* Author Prototype Object. */
var Author = clone(Person);
Author.books = []; // Default value.
Author.getBooks = function() {
return this.books;
}

var author0 = clone(Author);
author0.name = 'Dustin Diaz';
author0.books = ['JavaScript Design Patterns'];
var author1 = clone(Author);
author1.name = 'Ross Harmes';
author1.books = ['JavaScript Design Patterns'];

Here is the clone function. First the clone function creates a new and empty function, F. It then sets the prototype attribute of F to the prototype object. Lastly, the function creates a new object by calling the new operator on F. The cloned object that is returned is completely empty, except for the prototype attribute, which is (indirectly) pointing to the prototype object, by way of the F object.

/* Clone function. */
function clone(object) {
function F() {}
F.prototype = object;
return new F;
}


In classical inheritance, each instance of Author has its own copy of the books array. You could add to it by writing author1.books.push('New Book Title'). That is not initially possible with the object you created using prototypal inheritance because of the way prototype chaining works. A clone is not a fully independent copy of its prototype object; it is a new empty object with its prototype attribute set to the prototype object. When it is just created, author1.name is actually a link back to the primitive Person.name. When you write to author1.name, you are defining a new attribute directly on the author1 object.


var authorClone = clone(Author);
alert(authorClone.name); // Linked to the primative Person.name, which is the
// string 'default name'.
authorClone.name = 'new name'; // A new primative is created and added to the
// authorClone object itself.
alert(authorClone.name); // Now linked to the primative authorClone.name, which
// is the string 'new name'.
authorClone.books.push('new book'); // authorClone.books is linked to the array
// Author.books. We just modified the
// prototype object's default value, and all
// other objects that link to it will now
// have a new default value there.
authorClone.books = []; // A new array is created and added to the authorClone
// object itself.
authorClone.books.push('new book'); // We are now modifying that new array.


Classical inheritance is well understood, both in JavaScript and the programmer commu-
nity in general. Almost all object-oriented code written in JavaScript uses this paradigm. If you
are creating an API for widespread use, or if there is the possibility that other programmers not
familiar with prototypal inheritance will be working on your code, it is best to go with classical.

JavaScript is the only popular, widely used language that uses prototypal inheritance, so odds
are most people will never have used it before. It can also be confusing to have an object with
links back to its prototype object. Programmers who don’t fully understand prototypal inheri-
tance will think of this as some sort of reverse inheritance, where the parent inherits from its
children. Even though this isn’t the case, it can still be a very confusing topic. But since this
form of classical inheritance is only imitating true class-based inheritance, advanced JavaScript
programmers need to understand how prototypal inheritance truly works at some point any-
way. Some would argue that hiding this fact does more harm than good.

No comments:

Subscribe in a Reader