jmnf 5: Objects part 3

javascript magic-ninja-foo episode 5: Objects part 3 prototypes.

While there is some argument as to what makes up an object oriented language, I'm going to use the following characteristics: it must have data structures that have state and behavior and it must support inheritance, encapsulation, and polymorphism. I've already shown that javascript has a notion of objects with state and behavior.

Continuing down the list of OO characteristics is inheritance, a topic I believe to be a source of significant confusion in javascript. Hopefully by now, I've already cleared up some of the confusion by showing how javascript's notion of an object differs from a strictly class-based object-oriented language. Those distinctions are important when considering inheritance.

In Java, C++ or other class based object oriented languages, the class definition can indicate to the language that a class wants to inherit from another class. This 'class based' inheritance is obviously impossible for a language with no formal notion of classes. So how does javascript inheritance work?

To begin to understand javascript inheritance, we return to the idea of a constructor function. Consider the following code:

/*constructor function*/
function Orange(size,ripeness) {
   this.color = "orange";
   this.type = "citrus";
   this.size = size;
   this.ripeness = ripeness;
}
for(var prop in Orange) {
 alert(prop+":"+Orange[prop]);
 }

Look carefully at this code excerpt. Notice that I've defined a constructor function but I've not actually instantiated any objects with it (there is no myOrange in the code snippet). The second part of the code listing is enumerating the properties of an object, but what object? Remember, functions are a special kind of object in javascript. The code is enumerating the properties of the constructor function object itself, "Orange".

If we enter and execute this code we are returned something similar to the following:

prototype:[Object]

What is that and where did it come from? All functions in javascript (at least beyond 1.3) have a property automatically generated for them called 'prototype'. In this particular case, it's just a property with an empty object associated with it.

Manipulating the prototype property is how javascript accomplishes inheritance. Every constructor function can be assigned an object to its prototype property. This object becomes a 'template' from which properties can be inherited.

I made a point of saying in my previous article that the 'new' operator did several things. While I described the fact that it initializes a blank object and runs a constructor function against that blank object, I mentioned (but didn't describe) that it had another important role.

The new operator creates a link between the newly created object and the prototype object referenced by the constructor function it uses. It's important to make the distinction that it doesn't actually copy the properties from the template, it merely makes a link to them.

/*object literal with two properties*/
var animal = {sleeps:"",eats:""};
/*constructor function*/
function WildAnimal() {
   this.willAttack = true;
   this.hunts = true;
   }
/*assigning the Animal *object* to the 
  Constructors prototype*/
WildAnimal.prototype = animal;
/*make a new WildAnimal*/
var cheetah = new WildAnimal();
for(var prop in cheetah) {
 alert(prop+":"+cheetah[prop]);
 }

The newly created 'cheetah' object will not only have the properties defined in the 'WildAnimal' constructor but it will also appear to have all of the properties of the prototype 'animal' object. As you can see when the second part of the listing executes, the properties that are enumerated for the Cheetah object return:

willAttack:true
hunts:true
sleeps:
eats:

This notion of prototype inheritance can be extended to as many objects as you want. If I created another constructor called "WildCat" and created a new "WildAnimal" object, I could assign it to the constructor. This hierarchical prototype inheritance is called a prototype chain.

You can see where some of the confusion arises from the deviation from class style object oriented languages. So far we've been using constructor functions in a manner similar to a class in other languages. However, when we use the prototype property, we're now getting part of our object definition from another object. In other words, the constructor function as class idea starts to break down a bit because we're not inheriting properties from another constructor/class, but from something already instantiated.

This behavior leads to another of the "weird but neat" aspects of javascript. Remember that the properties are not actually copied, and that objects in javascript are fluid in nature. This means we can dynamically alter the "prototype" object, and all objects that "inherited" from it automatically reflect the changes.

/*object literal with two properties*/
var animal = {sleeps:"",eats:""};
/*constructor function*/
function WildAnimal() {
   this.willAttack = true;
   this.hunts = true;
   }

WildAnimal.prototype = animal;
var cheetah = new WildAnimal();
for(var prop in cheetah) {
   alert(prop+":"+cheetah[prop]);
   }

/*altering the prototype object*/
animal.dirty = "definitely";
for(var prop in cheetah) {
   alert(prop+":"+cheetah[prop]);
   }

In this case, we've altered the 'animal' object after 'cheetah' has been created. When the cheetah objects properties are enumerated the second time, the new property will be listed.

What happens if an object alters one of the properties it inherited? When this occurs, the object creates a new property that masks the prototype objects property. You can see the importance of this asymmetry from the above code. If the same prototype object is used by multiple objects for inheritance, allowing the object to overwrite its prototype object would affect anyone in the prototype chain for that object (probably not what you intended).

There's a more to be said about inheritance, but I'll save that for another article.

Next Episode