The Linux Page

Classes in JavaScript (from complex objects to proper classes)

When creating objects (commonly called JavaScript classes) in your JavaScript code, you want to use the prototype. This is the only clean way of doing it. In JavaScript, you can create objects on the fly, but those are not considered safe.

Quick and Dirty

The quick way is to create an object directly. This works, but it has potential problems with used against a powerful optimized such as the Google Closure Compiler.

All the Quick and Dirty examples can make use of this in their myFunc examples. However, the closure compiler will warning about all of them because it doesn't know for sure that it is the correct object that is going to be referenced (even if it is in all browsers, it is important when optimizing.)

Fully dynamic

var myClass = new Object();
myClass.myVar = "Hello!";
myClass.myFunc = function() { ... };
// myClass is an instance

One problem with the fully dynamic method, if you want to create multiple instances, you'll get multiple copies of myFunc even though one would have been enough. For singleton, obviously, that's not a concern.

Inline Declaration

var myClass = {
  myVar: "Hello!",
  myFunc: function() { ... }
};
// myClass is an instance

The inline declaration is also a fully dynamic definition. The syntax simply allows you to write everything within the curly brackets and thus without having to repeat myClass all the time.

Global Declaration

function myClassFunc() { ... };
function myClass()
{
  this.myVar = "Hello!";
  this.myFunc = myClassFunc;
}
var instance = new myClass();

In this case the function is assigned on construction. One big problem here is that myClassFunc is a global function so anyone can call it from anywhere any time. This is not safe.

Scoped Declaration

function myClass()
{
  this.myVar = "Hello!";
  this.myFunc = function() { ... };
}
var instance = new myClass();

In this case the function is safely defined inside the constructor of the object. However, for each instance we'll create a new copy of function(), very similar to the Fully Dynamic example.

Clean Prototyping

The actual proper way of creating an object in JavaScript is to make use of prototypes. This method always attaches the this reference to the correct object because the prototype declarations just cannot be incorrectly applied.

"Manual" Definitions

function myClass()
{
  this.myVar = "Hello!";
}
myClass.prototype.myVariable = "Variable coming from the prototype";
myClass.prototype.myFunc = function() { ... };
var instance = new myClass();

The simplest way to declare functions using the prototype mechanism is to declare each function one by one and assigning them to a member defined as a prototype as shown above. Any number of functions can be attached in this way. You can also declare variable members which should be viewed as read-only although JavaScript always sees everything as dynamic, really.

Define All In One

function myClass()
{
  this.myVar = "Hello!";
}
myClass.prototype = {
  myVariable: "Variable coming from the prototype",
  myFunc: function() { ... }
};
var instance = new myClass();

This is actually what is used in most cases. Notice that the declarations are separated by commas and the names are defined before a colon, just like the Inline Declaration above.

This last example is what you are expected to use, always. The myClass can further be placed in a namespace using the syntax decribed in Namespace Protected below.

Namespace Protected Definition

myNamespace = {};
myNamespace.myClass = function()
{
  this.value = "Hello!";
}
myNamespace.myClass.prototype = {
  myFunc: function() { ... }
};
var instance = new myNamespace.myClass();

Now the myClass does not appear in the global scope. If you are creating a large system with many definitions (many JavaScripts) and want to allow for growth by others with separate systems, this is probably the best way to go. Of course, this is only if you choose a good namespace.

Classes are also about Deriving from one another

In JavaScript, a class is a function which is given a prototype set of functions and variables. To derive such a thing, JavaScript creates what is called a prototype chain. Like some other languages, this means you are limited to derive from one class only (class A extends B). For this reason we also have the concept of templates that can be applied. You can derive from multiple templates, however, templates only offer pure virtual functions (no data or function implementations.)

Derivation is archieved by using the Object.create() function. This is very similar to the "new" operator, only it does not call the constructor. This duplication of an object generates a sub-prototype that you can link to your new class prototype:

function BaseClass() { ... }
function OtherClass() { ... }
OtherClass.prototype = Object.create(BaseClass);

This gives OtherClass access to all the functions and variables defined in the prototype of BaseClass.

To be more complete, though, you may need to call the super class functions from the child. This can be achieved by using the call() function if you save a pointer to the super class. For example:

OtherClass.superClass_ = BaseClass.prototype;

With that information at hand, you can call a function in the super class without having to know what the superclass is:

OtherClass.superClass_.someFunction.call(this);

Importantly, you also want to have access to the constructor. You may save a reference of the constructor in the child's class as in:

OtherClass.prototype.constructor = BaseClass;

This way the most derived constructor can call its parent, the parent its parent, etc. up to the base class constructor ensuring proper initialization of your objects:

function OtherClass()
{
    OtherClass.superClass_.constructor.call(this);
}

In Snap! Websites, I created two functions. One is the base() function which is a static function used to mark a constructor as a base class. If you are to derive from that class, you must call this function:

snapwebsites.base = function(base_class)
{
    base_class.prototype.constructor = base_class;
};

As we can see, it sets up the constructor; a reference back to the base class function. Similarly, I have another function to mark a class (child_class) as inheriting from another (super_class):

snapwebsites.inherits = function(child_class, super_class) // static
{
    /**
     * @constructor
     */
    function C() {}

    if(Object.create)
    {
        child_class.prototype = Object.create(super_class.prototype);
    }
    else
    {
        C.prototype = super_class.prototype;
        child_class.prototype = new C();
    }
    child_class.prototype.constructor = child_class;
    child_class.superClass_ = super_class.prototype;
};

As we can see, this version has a special case in the event Object does not have a create() function. This happens on older browsers. In that case we use some intermediate function to be able to create a prototype cascade.

To make use of those two functions, one does the following:

function BaseClass() { ... }
snapwebsites.base(BaseClass);
BaseClass.prototype.otherMethod = function() { ... }; // virtual

function OtherClass()
{
    OtherClass.superClass_.constructor.call(this);
    ...
}
snapwebsites.inherits(OtherClass, BaseClass);
OtherClass.prototype.otherMethod = function() // virtual
{
    ...
    OtherClass.superClass_.otherMethod.call(this);
    ...
};

As we can see, you are still responsible for calling the previous level methods. The problem is that those methods may have any number of parameters and trying to get that to work in JavaScript means many lines of code when the simple call() function as presented works 99% of the time and is ONE line of code.

Well... one of those days I'll look into getting a compiler that takes standard class (as defined in AS3) and transform those nice classes in this JavaScript prototype "crap". At that point, it can be as optimized as possible and all the weird things I present here will be done by the compiler. It will be neat.