Grauw’s blog

A long time ago I posted about object oriented programming and creating classes in JavaScript. That being a bit outdated, and me knowing a lot more about JavaScript now, I decided to make another post on that topic.

It is possible to produce OOP class-like definitions using JavaScript functions and prototypes. What follows is a description of syntax, best practices and oddities of this. The syntax used here uses no external libraries.

Update: I changed the constructor function notation from var MyClass = function() {}; to function MyClass() {}. The latter will have a name property set on it which is useful for debugging and on par with the native JS objects (e.g. Array.name == "Array"). It also has the benefit of being available in the entire scope. Additionally, I removed the public properties exception.

Creating classes

JavaScript classes and methods are created in the following manner:

function MyClass() {
    // constructor
}

MyClass.prototype.myMethod = function() {
    // method
};

MyClass.myStaticMethod = function() {
    // class method (also known as static method)
};

Convention for class names is to start with an upper-case letter (CamelCase), and members start with a lower-case letter (camelCase).

You instantiate an object using the new operator: new MyClass().

Documenting classes

Class and member documentation is created using JSDoc annotations, like so:

/**
 * Constructor description
 * @param option The first argument
 * @class
 * Class description
 */
function MyClass(option) {
    // constructor
}

/**
 * Method description
 * @param {Array} list The first argument
 * @return {boolean} The result
 */
MyClass.prototype.doSomething = function(list) {
    // method
};

It is recommended to create these always. The example’s method documentation also shows optional type annotations. Primitive types are written using lowercase: number, boolean and string, and object types as one would expect: MyClass, Array, Function and Object.

Creating object properties

Object properties should always be initialised in the constructor, so that you can use the constructor as a reference of which properties the object has, and so that the object is always in a defined state:

function MyClass() {
    this.myProperty = null;
}

MyClass.prototype.getMyProperty = function() {
    return this.myProperty;
};

MyClass.prototype.setMyProperty = function(value) {
    return this.myProperty = value;
};

Properties should generally be considered private; some people like to make this explicit by using a _ prefix. This is for reasons of encapsulation; modern JavaScript runtimes do support property getters and setters but these do not limit visibility nor do they have convenient syntax. So Java’s methodology of creating getSomething() and setSomething() getter/setter methods is adopted. Encapsulating internal state using accessor is essential to be able to deal with changing requirements, refactoring and performance optimisation in a straightforward manner and without compatibility problems.

Extending classes

In JavaScript you can not express inheritance directly. To inherit from another class you need a small extend() utility function:

function MyClass() {
    MyBaseClass.call(this);    // call super
}

extend(MyBaseClass, MyClass);

MyClass.prototype.doSomething = function(someValue) {
    MyBaseClass.prototype.doSomething.call(this, someValue);    // call super
};

/**
 * Class extension utility function
 * @param base The base class
 * @param constructor The constructor function for the new class
 */
function extend(base, constructor) {
    var prototype = new Function();
    prototype.prototype = base.prototype;
    constructor.prototype = new prototype();
    constructor.prototype.constructor = constructor;
}

Here, MyClass inherits from MyBaseClass, and MyClass’s constructor invokes the parent object’s constructor. The doSomething() method shows how to call a base method.

You can check whether something is an instance of a certain class using the instanceof operator, e.g. new MyClass() instanceof MyBaseClass ? 'yes' : 'no'.

Grauw

Comments

None.