public
Last active

Concise discussion on JavaScript class patterns, the whys and don'ts.

  • Download Gist
JavaScript Class Patterns.md
Markdown

Throughout this article, I will use Java's Object-Oriented Programming terminologies such as static, private and public.

Pattern 1: Self-construction

This pattern exploits scoping to allow for private and public fields and methods. It is named as Self-construction because the class function (or constructor) has to assign instance methods one by one.

Employee = (function(){

  // private static field
  var numEmployees = 0;

  // class function a.k.a. constructor
  function cls()
  {
    // private instance fields
    var name = "";
    var self = this;

    // public instance field
    this.age = 10;

    numEmployees++;

    // private instance method
    function incrementInternal()
    {
        // must use self instead of this
        self.age ++;
    }

    // public instance method  
    this.getName = function(){
        return name;
    };

    this.setName = function(newName){
        name = cls.capitalize(newName);
    };

    this.increment = function(){
        incrementInternal();
    };

    this.getAge = function(){
        return this.age;
    };
  }

  // public static field
  cls.staticVar = 0;

  // public static method
  cls.capitalize = function(name){
      return name.substring(0, 1).toUpperCase() + 
          name.substring(1).toLowerCase();
  };

  // private static method
  function createWithName(name)
  {
    var obj = new cls();
    obj.setName(cls.capitalize(name));
    return obj;
  }

  return cls;
})();

john = new Employee();
john.setName("john");

mary = new Employee();
mary.setName("mary");
mary.increment();

alert("John's name: " + john.getName() + ", age==10: "+john.getAge());
alert("Mary's name: " + mary.getName() + ", age==11: "+mary.getAge());

Note that Employee is actually the cls function that is returned.

A function defines a new environment and so each instance of Employee has its own copies of name and self. The fields name and self are private because they are local to Employee's scope, and hence inaccessible externally. The method incrementInternal is also private for the same reason.

The only downside is that the instance methods have to be assigned one by one for each new instance, instead of using Employee.prototype to auto-assign.

Inheritance

Manager = (function(parentCls){

  function cls(arg1)
  {
    // call parent's constructor to initialize instance methods and fields
    parentCls.call(this, arg1);

    // Save a copy of parent's method before overriding.
    var oldGetName = this.getName;
    this.getName = function(){
      // call parent's method
      return oldGetName.call(this).toUpperCase();
    };
  }

  // copy parentCls's static fields and methods to cls
  // If using jQuery, can be achieved alternatively using: 
  // $.extend(cls, parentCls);

  cls.createWithName = parentCls.createWithName;
  cls.staticVar = parentCls.staticVar;

  return cls;

})(Employee);

mgr = new Manager();
mgr.setName('john');
alert("John's name in uppercase: "+mgr.getName());

Note that this is not true inheritance since cls.staticVar and parentCls.staticVar are separate copies. If you update one, the other won't change. To illustrate:

Manager.staticVar ++;

alert(Employee.staticVar); // returns 0

This behavior can be explained by the fact that a "class" in JavaScript is actually an object. Subclasses of a class are merely copies of the parent class object with possibly additional fields and methods.

There is no way to make both staticVar's to be the same copy. The workaround is to change staticVar to be a private static field in the parent class and encapsulate (or protect) the field so that changes to the value of staticVar have to go through a method.

On the other hand, if staticVar references an object, then cls.staticVar and parentCls.staticVar would reference the same object, and changes to the properties of that object will be visible through both staticVar's.

Singleton

A singleton class is one that has only a single instance of it, logically. What I mean by logically is that in JavaScript, we can achieve singleton-ness by not using the traditional method of instantiating a single copy and preventing the class from being instantiated further. In JavaScript, we don't even have to define a class and instantiate it. The singleton "instance" can simply be an object, not a true instance of a class, as illustrated below:

Singleton = (function(parentCls){
  // private static field
  var field;

  var cls = {};

  // TODO : copy fields and methods from parentCls to cls
  // If using jQuery:
  // var cls = $.extend({}, parentCls);

  // private static method
  function method2(){
    cls.method1();
  }

  // public static field
  cls.field = 0;

  // public static method
  cls.method1 = function(){};

  cls.method2 = function(){
    cls.method1();
  };

  return cls;
})(parentCls);

If there is no parent class to inherit from, simply use {} for parentCls.

The benefit of using an object as a singleton is that new Singleton() will result in an error since Singleton is not a function, thereby preventing multiple instances.

Pattern 2: Objectified

Same as Pattern 1, but without using the this and new keywords.

Employee = (function(){
  function cls()
  {
    var name = '';
    var age = '';

    var obj = {
      publicField : 0,
      getName: function(){return name;},
      increment: function(){obj.publicField++;}
    };

    return obj;
  }

  return cls;
})();

john = Employee();     // no new needed
mary = new Employee(); // doesn't hurt, but less efficient

If we do not use new, this in Employee will refer to something else, for example, the Window object if the script is executed in a browser. But in general, what this is, depends on the surrounding context.

The new keyword assigns this to a copy of Employee.prototype upon entrance to the Employee function, and causes the function to return this copy by default if the function doesn't return anything. If we don't use this, then this copy is wasted.

Inheritance

Manager = (function(parentCls){

  function cls()
  {
    var obj = parentCls();

    var oldGetName = obj.getName;
    obj.getName = function(){
      return oldGetName().toUpperCase();
    };

    return obj;
  }

  return cls;
})(Employee);

Pattern 3: Prototypical

This pattern relies on a function's prototype object.

Employee = (function(){
  // private static var
  var staticVar;

  // private static method
  function staticMethod(){}

  function cls()
  {
    // public instance field
    this.name = "";
  }

  // public static method
  cls.staticMethod = function(){};

  cls.prototype = {
    // public instance method
    getName: function(){ return this.name;  }
    // ...
  };

  return cls;
})();

The main problem is that there seems to be no way to define private instance fields and methods. The reason is that the functions in the prototype object has to access the instance fields and methods through this. But defining fields as properties of this has a side effect of exposing the fields as public.

Inheritance

Manager = (function(parentCls){

  function cls()
  {
    this.type = 'Manager';  
  }

  cls.prototype = new parentCls();
  var proto = cls.prototype;

  // override parent's getName
  var oldGetName = proto.getName;
  proto.getName = function(){
    return oldGetName.call(this).toUpperCase();
  }; 

  // alternatively, since name is public, we can just use this.name
  proto.getName = function(){
    return this.name.toUpperCase();
  };

  return cls;
})(Employee);

Conclusion

Pattern 1 and 2 are very similar and equally powerful in allowing private fields and methods to be defined. The obj variable in Pattern 2 is in fact analogous to this in Pattern 1. The downside is that instance methods have to be manually assigned one by one but the performance impact should be minimal. For Pattern 2, inheritance is slightly less efficient since parentCls returns a new object each time it is called. This downside is absent in Pattern 1 since parentCls simply assigns instance methods and fields to an existing object.

On the other hand, for Pattern 3, there seems no way to define private fields and methods, thus exposing every instance fields to be public and leading to a violation of the data encapsulation principle.

Just a remark: why don't you use consistent bracket-style? Always write { on the same line, so you never run into this mistake:

function foo() {
    return
    {
        foo: "bar"
    }
}
foo(); // returns undefined, because "return" executes immediately without checking the next line

Thanks for your comments!

for "inline" functions such as Employee = function(){ ... I put the opening brace on the same line.
For standalone functions, I prefer putting the brace on a separate line to allow more breathing space. It's just a personal preference which is not the focus here.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.