Articles about Web Application Development

Classes in Javascript? Absolutely

By Justin Naifeh on 07-May-08 02:32:20 PM

After reading a brief overview of the proposed additions to Javascript 2.0 and the ensuing mixed reactions, it became apparent that many developers opposed class support as an addition to the language specification. Inheritance, encapsulation, and modularization are all beneficial features of classes (used correctly, of course), yet many Javascript developers have outright dismissed class support, often with utter contempt. If classes have proved beneficial in other languages, why wouldn't the same hold true for Javascript? Many developers feel that class support would detract from the dynamic power of Javascript.

Javascript uses a prototype-based mechanism to implement its OOP support, including object creation and inheritance. Combine this OOP underpinning with loose-typing, functions as first-class objects, closures, and a this reference that is bound based on object context, and you have a language that strongly contrasts to a more traditional OOP language like C++ or Java. That is not to say that any of those Javascript features is bad; instead, Javascript is so malleable that it can be shaped to emulate features of its class-based OOP counterparts.

Do you want class-like behavior? Use a function with the new operator to create a template for new objects:

// create Person function that acts as a class constructor
var Person = function(firstName, lastName)
{
  this.firstName = firstName;
  this.lastName = lastName;
};

// add a function to the Person prototype that will be accessible to all instances of Person
Person.prototype.introduce = function()
{
  alert("Hello, my name is "+this.firstName+" "+this.lastName);
};

var tommy = new Person("Tommy", "Thompson");
tommy.introduce(); // alerts "Hello, my name is Tommy Thompson"
  

Do you want to modularize your components and achieve encapsulation with private variables and functions? Achieve this by leveraging closures in what Douglas Crockford calls the module pattern:

var NS = {}; // create a namespace
NS.alertUtil = function()
{
  // private variable
  var message = "Error: A problem occured";

  // private function
  function _sendAlert(m)
  {
    alert(m);
  }

  // public function
  function sendAlert()
  {
    if(arguments.length > 0)
    {
      _sendAlert($A(arguments).join(", ")); // Prototype's $A utility function
    }
    else
    {
      _sendAlert(message);
    }
  }

  return {
    sendAlert : sendAlert // makes the sendAlert function public on the returned object
  };
}();

// alerts "Error: A problem occured"
NS.alertUtil.sendAlert(); 

// alerts "Error Code: 1234, Problem: I'm broken. Fix me"
NS.alertUtil.sendAlert("Error Code: 1234", "Problem: I'm broken. Fix me");
  

Do you want inheritance among your objects? Extend one object's prototype with another object's prototype then let the properties be discovered via the prototype chain.

// Person class
var Person = function(firstName, lastName)
{
  this.firstName = firstName;
  this.lastName = lastName;
};

// add a function to the Person prototype that will be accessible to all instances of Person
Person.prototype.introduce = function()
{
  alert("Hello, my name is "+this.firstName+" "+this.lastName);
};

// Employee class
var Employee = function(firstName, lastName, title)
{
  this.firstName = firstName;
  this.lastName = lastName;
  this.title = title;
};

Employee.prototype = new Person(); // extend the prototype

Employee.prototype.sayTitle = function()
{
    alert("I work as a "+this.title);
};

var employee = new Employee("Tommy", "Thompson", "Code Monkey");
employee.introduce(); // alerts "Hello, my name is Tommy Thompson"
employee.sayTitle(); // alerts "I work as a Code Monkey"
  

The last example illustrates something very important: although Javascript can model inheritance by traversing the prototype chain of an object, it still lacks native support for programmatically referencing the superclass/subclass relationship. More specifically, Javascript lacks native support to safely override methods and invoke overridden superclass methods from subclasses (as is done with super in Java). Because this is Javascript—a language where anything seems possible given enough clever language Ninjitsu—many popular libraries have emulated basic class-based OOP features. My favorite implementation was introduced in Prototype 1.6, although other libraries support excellent implementations as well.

// Person class

var Person = Class.create({
  initialize : function(firstName, lastName)
  {
    this.firstName = firstName;
    this.lastName = lastName;
  },

  introduce : function()
  {
    alert("Hello, my name is "+this.firstName+" "+this.lastName);
  }
});

// Employee class extends Person
var Employee = Class.create(Person, {
  initialize : function($super, firstName, lastName, title)
  {
    $super(firstName, lastName); // call to the parent constructor
    this.title = title;
  },

  sayTitle : function()
  {
    alert("I work as a "+this.title);
  } 
});

var employee = new Employee("Tommy", "Thompson", "Code Monkey");
employee.introduce(); // alerts "Hello, my name is Tommy Thompson"
employee.sayTitle(); // alerts "I work as a Code Monkey"
    

Prototype's class-based implementation provides that "at home" feeling with traditional OOP. But this brings up a point: with so many developers scrambling to implement their own class libraries, and if classes (supposedly) yield no benefit over prototype-based objects, then why are developers trying so hard to emulate them? Some claim that this desire for classes is a side-effect of newcomers to Javascript relying on their traditional OOP backgrounds in languages like C++ and Java. This might be true in some cases, though overall it seems inaccurate. Developers want classes because they do work.

The danger with the recent influx of class-based OOP implementations is a lack of consistency that wouldn't be present—or at least as glaring—if class support was innate to Javascript. When there exists dozens of class-based OOP implementations, each with a unique methodology and philosophy behind the code, there also exists an equal, if not greater, number of shortcomings, bugs, and maintainability issues. To be fair, this is speculation, but it seems realistic to expect such problems. (We've already seen an analogous problem in the web-standards domain where each browser only implements a subset of standards, often riddled with bugs, oddities, oddities, and even more oddities.) Consistency is key, something that would be ensured if classes were included in the Javascript language specification.

Without consistency, developers run into a mix and match problem with class definitions. For example, pretend an Ajax library exists that uses an instance of AjaxHandler (defined below) to make an Ajax request. The AjaxHandler class defines default onSuccess and onFailure methods as callbacks, along with properties that pertain to the specific request. The default onSuccess and onFailure methods simply alert "success" and "failure," respectively. These default callback methods are rather boring, but fortunately, the Ajax library encourages subclassing AjaxHandler to add extra functionality and behavior. So we subclass AjaxHandler with our own class, CustomAjaxHandler, which overrides its parent's onSuccess and onFailure methods to print basic test information to the document. To subclass AjaxHandler, we use Prototype's class implementation mentioned earlier, which will give CustomAjaxHandler access to its parent's properties and methods.

/* ----- start ajaxLibrary.js ----- */

/**
 * Default Ajax handler class. Feel free to extend this class if an alert is not sufficient
 * for your callbacks.
 */
var AjaxHandler = function(method, params)
{
  this.method = method; // 'GET' or 'POST'
  this.params = params; // Map of parameters to be sent to server.
};

AjaxHandler.prototype.onSuccess = function(transport)
{
  alert('success');
};

AjaxHandler.prototype.onFailure = function(transport)
{
  alert('failure');
};

AjaxHandler.prototype.getMethod = function()
{
  return this.method;
};

AjaxHandler.prototype.getParams = function()
{
  return this.params;
};

/* ----- end ajaxLibrary.js ----- */

/**
 * CustomAjaxHandler class to print test info do the DOM.
 */
var CustomAjaxHandler = Class.create(AjaxHandler, {
  initialize : function($super, method, params, testNumber)
  {
    $super(method, params);
    this.testNumber = testNumber;
  },
  onSuccess : function(transport)
  {
    $('successLog').innerHTML += 'test: ' + this.testNumber + ' status: ' +transport.status + '<br />';
  },
  onFailure : function(transport)
  {
    $('failureLog').innerHTML += 'test: ' + this.testNumber + ' status: ' +transport.status + '<br />';
  }
});

/* test code */

// Test 1 : read object with id 1234
var custom1 = new CustomAjaxHandler(1);
var params1 = {'action':'read','id':'1234'};
new Ajax.Request(custom1, params1);

// Test 2 : read object with id 5678
var custom2 = new CustomAjaxHandler(2);
var params2 = {'action':'read','id':'5678'};
new Ajax.Request(custom2, params2);
    

Everything in the above code looks good, but when executed, it never makes it beyond the following line:

var CustomAjaxHandler = Class.create(AjaxHandler, {

Because Prototype expects a superclass to have a specific format (an initialize method, among other things), an error occurs within the Prototype library since AjaxHandler doesn't conform to the format. This demonstrates an inconsistency in class definitions that leads to fragile hierarchies. As libraries grow in size and become more expandable, inconsistencies like the one demonstrated will plague development. Instead, imagine the simplicity of a singular class format: the intent of the code becomes clear, and the inheritance mechanism becomes unambiguous. (The following syntax is unofficial and meant for illustrative purposes.)

class AjaxHandler {
 
  var method;
  var params;

  init : function(method, params)
  {
    this.method = method;
    this.params = params;
  }

  // ... other class methods
}

class CustomAjaxHandler extends AjaxHandler {

  var testNumber;

  init : function(method, params, testNumber)
  {
    super(method, params);
    this.testNumber = testNumber;
  }

  // ... other class methods
}
    

Remember, class usage in Javascript 2.0 is optional, so a developer can choose to stick to its prototypical nature if desired. If classes were made available within the language, it would be reasonable to assume that most people would use them instead of dealing with prototype gymnastics to achieve the same effects as traditional OOP. Classes are an addition to the language, not a replacement or threat. But as Ajax becomes more popular, mashups more complex, and browser applications thicker, a need for large-scale application structure arises. The ability to cleanly separate components into classes under namespaces with a consistent inheritance design is a proven methodology to build maintainable large-scale applications that are robust and scalable.

Javascript is an immensely powerful language, a reality which seems to escape many programmers who simply consider the language "the snippet in the script tag that creates annoying popups." Yet how many other popular languages can implement non-native features such as function currying, Aspect-Oriented Programming, variable and function visibility modification, and many other impressive pieces of functionality? Clearly, the language is powerful. Javascript just needs a push into the next era of web-development, and adding class support to the language is a first step in the maturity process.