Grauw’s blog
Object-oriented programming in JavaScript
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.
This syntax is also picked up by Eclipse’s JSDT plugin, which will provide you with a convenient object outline, code completion and more. JSDT is packaged with Eclipse EE by default.
Creating classes
JavaScript classes and methods are created in the following manner:
var MyClass = function() {
// constructor
};
MyClass.prototype.myMethod = function() {
// method
};
MyClass.myStaticMethod = function() {
// 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, e.g. return new MyClass();.
Documenting classes
Class and member documentation is created using JSDoc annotations, like so:
/**
* Constructor description
* @param bOption The first parameter
* @class
* Class description
*/
var MyClass = function(bOption) {
// constructor
};
/**
* Method description
* @param {Array} oList The first parameter
* @return {boolean} The result
*/
MyClass.prototype.doSomething = function(oList) {
// method
};
It is recommended to create these always. The example’s method documentation also shows optional type annotations.
Creating class properties
Class 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:
var MyClass = function() {
this.myProperty = null;
};
MyClass.prototype.getMyProperty = function() {
return this.myProperty;
};
MyClass.prototype.setMyProperty = function(oValue) {
return this.myProperty = oValue;
};
Properties should generally be considered private, unless explicitly mentioned as public in the @class annotation. The reason for this is that JavaScript does not have property getters and setters (in all browsers), so Java’s methodology of creating getSomething() and setSomething() getter/setter methods is adopted. Accessor methods are essential to be able to deal with changing requirements, refactoring and performance optimisation in a straightforward manner and without compatibility problems.
Guideline for public properties: sometimes for convenience you want to make an exception. If you really must, direct references to other objects (Action.record) or primitive types (Channel.id, Field.value) are candidates for consideration. Avoid accessing arrays and hash maps directly though, so that you can easily change and optimise the implementation underneath. When in doubt you should use accessor methods.
Properties can also be defined directly on the prototype, however this is not recommended. Example:
var MyClass = function() {
};
MyClass.prototype.firstProperty = null; // okay (but beware!)
MyClass.prototype.secondProperty = 100; // okay (but beware!)
MyClass.prototype.thirdProperty = new OtherClass(); // bad
The reason why this is not recommended is because when a property is defined on the prototype, it will only be instantiated once. For null or primitive types (string, number, bool) this is not a problem. However for objects like in the third example, the OtherClass instance will not be unique for each new MyClass instance and be shared, risking unforeseen problems. Therefore, in order to not confuse those not aware of this detail, I consider this pattern as a whole bad practice.
Although not being recommended, the example provided above is given to provide better understanding of how JavaScript objects work, and for that occasional case where you really want to draw out that last bit of JavaScript performance.
Extending classes
To inherit from another class you need a small extend() utility function:
var MyClass = function() {
MyBaseClass.call(this); // call super
};
MyClass = extend(MyBaseClass, MyClass);
MyClass.prototype.doSomething = function(sArgument) {
MyBaseClass.prototype.doSomething.call(this, sArgument); // call super
};
/**
* Class extension utility function
* @param cBase The base class
* @param fConstructor The constructor function for the new class
*/
function extend(cBase, fConstructor) {
var cPrototype = new Function();
cPrototype.prototype = cBase.prototype;
fConstructor.prototype = new cPrototype();
fConstructor.prototype.constructor = fConstructor;
return fConstructor;
}
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. return new MyClass() instanceof MyBaseClass ? 'yes' : 'no';.
Namespacing
An object can be ‘namespaced’ like so:
myNamespace = new function() {
var MyClass = function() {
};
// export
this.MyClass = MyClass;
};
Here a global myNamespace namespace object is created, and at the end the local MyClass class is exported as a member of the namespace object.
Grauw
