Monday, February 10, 2014

In JavaScript Everything's an Object, Get Over It!

Today I am staying out of the philosophical discussion about whether JavaScript is truly an object-oriented language. FYI a lot of that discussion revolves around polymorphism and that because it is not strongly typed it does not have overload methods. That is true of other languages too, consider Groovy. But for the moment, let's just say that if we can actually create objects with properties then we can do some OOP with it.

In this article I am only interested in the tools that JavaScript has to build objects, and JavaScript is a bit schizophrenic about that. For example JavaScript defines the "new" operator so that creating an object looks more like programming Java. But we can also create objects literally or by using the grandparent of all objects: Object.

Let's take a tour of object creation and see if we can sort some all it out. First of all, we can define literal objects and properties using a comma-separated list of "name: value" pairs enclosed in braces. The property names should follow the identifier rules for JavaScript, and the value can be anything: a string, a number, an array, a reference to another object, or even a function. We can save a reference to the new object in a variable, and we can retrieve or assign new values to a property using the the variable and the . (dot) operator:

newline = '<br />';
Box = { width: 4, height: 5 };
document.write('The box is ' + Box.width + ' x ' + Box.height + newline);
document.write('The box volume is ' + (Box.width * Box.height) + newline);

Secondly, by default all JavaScript objects are extensible; all you have to do is assign a value to a new property and the object has it:

Box.depth = 6;
document.write('The box is ' + Box.width + ' x ' + Box.height + ' x ' + Box.depth + newline);
document.write('The box volume is ' + (Box.width * Box.height * Box.depth) + newline);


So, back to that new operator. It works with a function and builds a new object:

function Rectangle() {
}

var shape = new Rectangle();


The part that is often misunderstood is that line eleven in the example doesn't create a new "Rectangle"; it creates a new empty object and then executes the Rectangle function in the context of the new object. In the function the keyword this is a reference to the object being created. Any property can be added to this in the function, because by default all JavaScript objects are extensible. So, the Rectangle function becomes the constructor method for the new object.

A closure is a function defined within a function. The closure may not be used right away, in fact most of them are meant to be called back long after the enclosing function has finished. The really cool thing is that all of the variables in the scope of the enclosing function are available in the closure, even when the closure is invoked after the enclosing function has exited! To make this work JavaScript saves a copy of the variables alongside the closure, with the values that they had when the enclosing function exits.

Unfortunately, purely using closures inside of a function that acts as a "constructor" is an example of trying to force the language to look like something it isn't.  Syntactically it resembles Java superficially. In this example the w and h variables are preserved for the volume function even when it is called long after the Rectangle function has finished (inside the Box):

function Rectangle(width, height) {

@tab;var w = width;
@tab;var h = height;

@tab;this.volume = function() { return w * h; }
}

function Box(width, height, depth) {

@tab;Rectangle.call(this, width, height);
@tab;var d = depth;

@tab;this._volume = this.volume;
@tab;this.volume = function() { this._volume() * d; }
}

var shape = new Box(4, 5, 6);
document.write('The volume of shape = ' + shape.volume() + newline);


So it works. A few notes to make sure that we are clear on everything that just happened:
  • The variables w and h are not actually properties in the new object, but they are preserved for the context of the volume function whenever it is called. Every object created with Rectangle has another copy of a w and h for the function.
  • volume is a property of the object, it has a reference to the anonymous function assigned to it.
  • An object constructed using Box also has everything that the Rectangle function builds, because the Rectangle function was called from Box. That mimics inheritance.
  • The Rectangle function used in Box needs to be called with the context of the new object that Box is initializing; the way to do this is to use the call method on the function. The call method accepts an object as the first parameter and then calls the function in the context of that object.
  • Take a close look at line 25: before we can override the volume method on the next line we need to save a reference to the volume method that the Rectangle constructor already built for us. If we didn't do that we wouldn't be able to find the function again in the override.
You may have noticed that we completely ignored method overloading where several functions all share the same name but use different parameters. That works in strongly typed languages, but without parameter types we cannot match up the correct function to call in a prototypal language. We can fake it for the client code by making a function figure out the parameters and then deciding how to do the work. But then we are adding a lot to the program just to simulate overloading, and frankly overloading is not a fundamental concept of OOP. JavaScript doesn't have overloading, so don't go there.

On the surface we have everything we need in the last example: the simulation of private data members (w, h & d), public methods (volume), inheritance by calling the base-constructor Rectangle in the context of Box, and method overriding with a new volume function defined in Box.

But... memory is going to suffer because new copies of the closures are created every time a Rectangle or Box is created. And even more memory is used to preserve the variables in the scope of each of the closures created. And then there is a more subtle problem: there isn't any way to ensure what an object is based on after it has been created. So while the technique looks pretty close to other object-oriented languages it really isn't what JavaScript is about. You probably shouldn't be writing JavaScript programs this way.

So JavaScript is what it is. And I'll argue with anyone until my face is blue that it is an object-oriented programming language. It just may not be what you expect for OOP. That may be because we've all been brainwashed into thinking that the only way to do OOP is by defining classes and instantiating them. You have to do a paradigm shift away from classes to use the full power of JavaScript because it is a prototypal language.

In a prototypal language all objects leverage another object, their prototype. Each JavaScript object has it's own reference to a prototype object. When you assign a value to an object property, it is set in the object. But when a request is made against an object for a property value and it isn't found, then JavaScript checks the prototype to see if that has the property before it decides the request is an error. So the prototype implements the DRY principle; it allows many objects to share the same properties (data and methods) and then override them as needed (Hunt). It also relies on Henry Lieberman's definition of delegation, which provides the rules of how an object and prototypes are examined in turn looking for a property (Lieberman).

This example explains what we would like to happen with a Box and it's prototype; if the Box doesn't have doesn't have width or height we expect JavaScript to go to the prototype to get the values. We'd also like the Box volume method to be able to leverage the volume method in the prototype:


Adding prototypes alongside of constructors is built into JavaScript and is a commonly used way of implementing what we want. In this case a Rectangle has what the Box needs, so we make the Box prototype a new object created using a Rectangle. To create the prototype we didn't have any initial values to pass to the Rectangle function, so we just called it without any:

function Rectangle(width, height) {

@tab;this.width = width;
@tab;this.height = height;
}

Rectangle.prototype = {

@tab;width: null,
@tab;height: null,
@tab;volume: function() { return this.width * this.height; }
};

function Box(width, height, depth) {

@tab;Rectangle.call(this, width, height);
@tab;this.depth = depth;
}

Box.prototype = new Rectangle;
Box.prototype.depth = null;
Box.prototype.volume = function() { return Rectangle.prototype.function.call(this) * this.depth; };

var shape = new Box(4, 5, 6);

document.write('The volume of shape = ' + shape.volume() + newline);


This works for two reasons. First, since functions are objects in JavaScript they can have properties, and in this case both Rectangle and Box have a prototype property. Second, when the new operator is used JavaScript looks at the function object for the prototype property, and then assigns that reference to the prototype of the new object. The prototype property is not the same as the prototype reference in an object; the reference is often stored in an inaccessible property named [[prototype]] or something similar. So when a new object is created with Box then its prototype object references the same object that Box.prototype references.


Every prototype is an object, so each one has its own prototype, creating a chain of objects. If you look closely at the linkage, the objects created from Box have a prototype that is Box.prototype, and Box.prototype has a prototype that is Rectangle.prototype. JavaScript checks every object in the prototype chain when it tries to resolve a property.

So one benefit we don't know about before is the instanceof operator. This operator checks against the entire prototype chain to see if an object has a reference to another function object's prototype property:

document.write('shape instanceof Box = ' + (shape instanceof Box) + newline);
document.write('shape instanceof Rectangle = ' + (shape instanceof Rectangle) + newline);


Another benefit is DRY: the functions are now shared in the prototype objects. But you may have noticed a few problems. We are still depending on leveraging the Rectangle function inside the Box constructor. All we have really done is move the functions from the constructors into the prototypes. We declared the width, height, and depth properties in the prototypes too, but just to be safe in case the constructors didn't make them before somebody asked. But on the flip-side the whole thing has become confusing: we have to manage the constructors and their prototypes in parallel now.

There is another detriment to moving the methods into the prototypes: they aren't closures anymore and we loose the simulation of private data members that the closures brought us. JavaScript simply doesn't have any concept of privacy for properties. It is what it is, so go with the flow. The community has adopted an identifier hint to help though: an object property name that starts with an underscore is meant to be private. Respect it and don't touch it. We actually snuck one in a couple of examples ago on line eighteen!

It turns out that the need to build objects like different sized Rectangles using a constructor is so 20th century. Looking at it another way, the dependency on using constructors with parameters is a bad thing because it locks the client code into creating a specific object based on specific data. But if the client code expects something that has the properties of the Rectangle, couldn't we also give it a Box? A Box has all the properties and functionality of a Rectangle. That's what polymorphism is all about: to abstract what the object really is away and let the client code treat the object as a Rectangle even if it is really something else.

So in modern OOP we use the Dependency Inversion principle (Martin). It's a really simple implementation where we modularize the creation of objects away from the program that uses them; the program is provided the polymorphic objects it needs after they've been created somewhere else. Where should we do this? Quite likely we are going to isolate the AJAX code that loads objects from a remote server from the client code that uses those objects! This module won't use parameterized constructors; instead it will create the right kind of object and populate the properties directly from the data. This is what our example might look like without the parameterized constructors:

function Rectangle() {
}

Rectangle.prototype = {
@tab;width: null,
@tab;height: null,
@tab;volume: function() { return this.width * this.height; }
};

function Box() {
}

Box.prototype = new Rectangle;
Box.prototype.depth = null;
Box.prototype.volume = function() { return Rectangle.prototype.function.call(this) * this.depth; };

var shape = new Box;

shape.width = 4;
shape.height = 5;
shape.depth = 6;

document.write('The volume of shape = ' + shape.volume() + newline);


That was one problem solved, but we still have this dual constructor-prototype thing going on. And now our constructors are empty! Well, JavaScript has a better way to handle this: Object.create. Instead of using the new operator, Object.create will create an empty new object and assign another object as its prototype. But it's one of the schizophrenic parts of JavaScript because it's a completely different approach, and without constructors it doesn't play well with the new operator:

Rectangle = {

@tab;width: null,
@tab;height: null,
@tab;volume: function() { return this.width * this.height; }
};

Box = Object.create(Rectangle);
Box.depth = null;
Box.volume = function() { return Rectangle.volume.call(this) * this.depth; };

var shape = new Box;

shape.width = 4;
shape.height = 5;
shape.depth = 6;

document.write('The volume of shape = ' + shape.volume() + newline);


This example created a simple chain of objects: Box chains to Rectangle, shape chains to Box, and so on. This the whole point of what the prototypal thing is about: if a group of objects all share the same prototype, then they all function in a similar way. And that brings us polymorphism. This is what JavaScript is. The key is to recognize that every object is unique with its own properties and prototype. There are no classes that provide a template for how an object should work; an object works because it or its prototype chain provides the expected functionality. Objects with similar prototype chains have similar behavior.

But we aren't finished yet. We still have that nasty syntax of assigning properties individually to Box after we create it and link it to Rectangle. It turns out that Object.create has a second parameter which is itself an object that defines any properties the new object should have. Usually we pass that as an anonymous, literal object. But, the properties are defined using property descriptors, which are objects where the properties define how each property should behave:

Rectangle = Object.create(Object, {

@tab;width: { enumerable: true, writable: true, value: null },
@tab;height: { enumerable: true, writable: true, value: null },
@tab;volume: { value: function() { return this.width * this.height; }}
});

Box = Object.create(Rectangle, {

@tab;depth: { enumerable: true, writable: true, value: null },
@tab;volume: { value: function() { return Rectangle.volume.call(this) * this.depth; }}
});

shape = Object.create(Box);
shape.width = 4;
shape.height = 5;
shape.depth = 6;

document.write('The volume of shape = ' + shape.volume() + newline);


So finally we've reached a syntax that is true to the prototypal paradigm and gives us a clean way to define the properties and their values in a new object. This is what we should be using to do OOP in JavaScript!

We're not going any further down the road of defining properties using descriptors. You should investigate that more yourself if your not familiar with it. Object has six methods that help manage the properties and extensibility of any JavaScript object, and the properties to describe a property are defined in the documentation there.

But there are two little pieces left. First, we want to be a more flexible when we override the volume method in Box. What if we changed the underlying prototype and it was no longer Rectangle? Let's change the volume method to use getPrototypeOf and depend on the prototype, instead of depending on Rectangle. Pay attention to what we get the prototype of, Box, not this. We don't know what this will be a reference to when the method is called. That's OOP, we are changing the method to use an abstraction!

volume: { value: function() { return Object.getPrototypeOf(Box).volume.call(this) * this.depth; }}


The other piece is because instanceof doesn't work anymore when objects are defined using Object.create. instanceof uses the prototype property of the constructor. And now we aren't using constructors. So enter a new function, isPrototypeOf, which checks to see if one object is in the prototype chain of another object:

document.write('shape is a Box = ' + Box.isPrototypeOf(shape) + newline);
document.write('shape is a Rectangle = ' + Rectangle.isPrototypeOf(shape) + newline);


That about takes care of everything we needed to talk about. The examples show here may be download from as an Eclipse static web project: JavaScriptOOP.zip. If you are using another IDE then the html files can easily be copied to it, or they can be launched just using a browser. Enjoy!

References

See the references page.

1 comment: