Analyzing JavaScript Class Patterns Performance

Posted in: javascript

For a while now I’ve been considering different JavaScript class patterns and their performance when initializing and calling methods from them. Depending on the use-case some class patterns can show considerable performance improvements over the typical pseudo-classical patterns. In some cases, the use of closures and private member initializers in function constructors, as opposed to what’s stated by the MDC, can also show performance improvements over classical approaches.

JavaScript class performance can be analyzed by altering two variables: the JavaScript class can have public/private properties and/or public/private methods. On the other hand, code performance can be measured on class definition, class constructors, or instance methods.

I’ll just state the conclusion before making the analysis: there’s not a unique class pattern that will make your code faster in all cases. Most of the time it just depends on your use-case. In most cases people try to delegate all computations at class definition, since this generally happens only once. When this is not possible there’s a tradeoff between having computations in the class constructor or performing these computations when a method is called.

The premise for this analysis is that the extra-computations can’t be avoided, and their complexity can’t be simplified. In some sense, they’re just like a black box.

In the next section I’ll show how you can use closures, variable scope and a class prototype to delegate computations at class definition, class constructor or to just keep the computations when calling a method.

Class.prototype 101

Any JavaScript object, (String, Object, RegExp, etc), generally has two type of properties: the own properties and the properties found in the prototype chain. Each object has a special property proto that actually points to another object (or is null).

When we try to access a property in an object:

var propertyValue = object.myProperty;

it will first try to see if it’s an own property, something that can be checked by doing:

Object.prototype.hasOwnProperty.call(object, 'myProperty');

and use that property if it exists. If it doesn’t exist then it will try to find that property inside the object pointed by the proto property:

object.__proto__.myProperty;

If the property isn’t found in the object pointed by proto then it will try to find it recursively in the prototype chain (object.proto.proto, etc) until it gets to the top of it.

When we create a Class with a prototype:

function Person() {
    //constructor
 }

 Person.prototype.sayHi = function() {
    alert("hi");
 };

 var p = new Person();
 p.sayHi(); //"hi"

Internally JavaScript will assign to any Person instance proto property the Person.prototype object we defined above. This means that we actually define an object with methods only once, and any instance of Person will have a proto property pointing to those methods. Since all Person instances point to the same Person.prototype people usually say they share those methods, and so they’re shared properties across instances.

var p = new Person();
p.__proto__ === Person.prototype //true
p.sayHi === p.__proto__.sayHi //true

The convenience about this is that we don’t have to execute an entire code that actually re-creates methods and assigns them to a newly created object in the function constructor. Since each time new Person is called the Person function is executed, it would be better to just define the methods in the prototype object for once and not execute code for augmenting an object with new methods each time we call Person.

Let’s see how this could be used to increase the performance of certain class patterns.

Public Properties

In the public properties class definition we just move all properties definitions from the class constructor to the prototype object. By moving these computations we can see that there are some interesting performance improvements on constructor calls.

The code preparations are:

function ConstructorPublicProperties() {
  this.publicProperty1 = 1;
  this.publicProperty2 = 2;
  this.publicProperty3 = 3;
  this.publicProperty4 = 4;
  this.publicProperty5 = 5;
  this.publicProperty6 = 6;
  this.publicProperty7 = 7;
  this.publicProperty8 = 8;
  this.publicProperty9 = 9;
  this.publicProperty10 = (function() {
   var i = 100;
   while (i--);
   return 10;
  })();
}

function PrototypePublicProperties() {}

PrototypePublicProperties.prototype = {
  publicProperty1: 1,
  publicProperty2: 2,
  publicProperty3: 3,
  publicProperty4: 4,
  publicProperty5: 5,
  publicProperty6: 6,
  publicProperty7: 7,
  publicProperty8: 8,
  publicProperty9: 9,
  publicProperty10: (function() {
   var i = 100;
   while (i--);
   return 10;
  })()
};

The code we actually compare is:

for (var i = 0; i < 1000; i++) {
 new ConstructorPublicProperties();
}

vs.

for (var i = 0; i < 1000; i++) {
 new PrototypePublicProperties();
}

As you can see by the test case that you can run yourself moving computations to the prototype object can lead to considerable performance improvements in class constructors.

Public Methods

For public methods the case is just the same than with public properties. Moving all definitions and initializations to the prototype object leads to performance improvements in class constructors.

The preparation code for this is:

function ConstructorPublicMethods() {
  this.publicMethod1 = function(n) {
   return 2 * n;
  };

  this.publicMethod2 = function(n) {
   return 3 * n;
  };
 }

 function PrototypePublicMethods() {}

 PrototypePublicMethods.prototype = {
  publicMethod1: function(n) {
   return 2 * n;
  },

  publicMethod2: function(n) {
   return 3 * n;
  }
 };

The test code is:

for (var i = 0; i < 1000; i++) {
 new ConstructorPublicMethods();
}

vs.

for (var i = 0; i < 1000; i++) {
 new PrototypePublicMethods();
}

As you can see constructors are also faster when public methods are defined inside the prototype object.

Private Methods

Private methods can be defined in JavaScript by using the class constructor as encapsulation for other function definitions. This is a pattern I’ve seen done by Douglas Crockford and is used in many other places:

function ConstructorPrivateMethods() {
  function privateMethod1(n) {
   return 2 * n;
  }

  function privateMethod2(n) {
   return 3 * n;
  }

  this.publicMethod1 = function(n) {
   return privateMethod1(privateMethod2(n));
  };
 }

If you’re only defining private methods (and not private properties) you can also augment the prototype object with public methods and use encapsulation for defining private methods around the prototype object. I’ve seen this clever technique done by WebReflection some time ago:

function PrototypePrivateMethods() {}

 (function() {

  function privateMethod1(n) {
   return 2 * n;
  }

  function privateMethod2(n) {
   return 3 * n;
  }

  PrototypePrivateMethods.prototype = {
   publicMethod1: function(n) {
    return privateMethod1(privateMethod2(n));
   }
  };

 })();

The code tested is the same as in the previous sections:

for (var i = 0; i < 1000; i++) {
 new ConstructorPrivateMethods();
}

vs.

for (var i = 0; i < 1000; i++) {
 new PrototypePrivateMethods();
}

As you can see for yourself the prototype version has the fastest constructor. The performance improvements are considerable with this pattern which is interesting in use-cases such as when defining some helper functions to be used with a class.

Private Properties

The examples above are kind of simple to analyze: moving the computations from the class constructor to the prototype object can lead to faster constructors, and more generally can increase performance since the code is computed once at the class definition and not once per each constructor call.

With private properties there’s a tradeoff. Since private properties are generally accessed and/or modified by public methods, defining a private property inside the constructor forces the definition of the public method into the class constructor too, decreasing the overall performance of the class constructor. Another interesting technique could be to just use an underscore prefix for private members:

function ConstructorPrivateProperties() {
  var privateProperty1 = 1,
      privateProperty2 = 2,
      privateProperty3 = 3,
      privateProperty4 = 4,
      privateProperty5 = 5,
      privateProperty6 = 6,
      privateProperty7 = 7,
      privateProperty8 = 8,
      privateProperty9 = 9,
      privateProperty10 = 10;

  this.publicMethod1 = function() {
   return privateProperty1
        + privateProperty2
        + privateProperty3
        + privateProperty4
        + privateProperty5
        + privateProperty6
        + privateProperty7
        + privateProperty8
        + privateProperty9
        + privateProperty10;
  };
 }

 function PrototypePrivateProperties() {}

 PrototypePrivateProperties.prototype = {
  _privateProperty1: 1,
  _privateProperty2: 2,
  _privateProperty3: 3,
  _privateProperty4: 4,
  _privateProperty5: 5,
  _privateProperty6: 6,
  _privateProperty7: 7,
  _privateProperty8: 8,
  _privateProperty9: 9,
  _privateProperty10: 10,

  publicMethod1: function() {
   return this._privateProperty1
        + this._privateProperty2
        + this._privateProperty3
        + this._privateProperty4
        + this._privateProperty5
        + this._privateProperty6
        + this._privateProperty7
        + this._privateProperty8
        + this._privateProperty9
        + this._privateProperty10;
  }
 };

The code we’re testing is:

for (var i = 0; i < 1000; i++) {
 new ConstructorPrivateProperties();
}

vs.

for (var i = 0; i < 1000; i++) {
 new PrototypePrivateProperties();
}

As you can see for yourself using the prototype object yields faster constructors again.

NOTE: The results for the next section seem to have been reverted around Chrome 25. What this means is that now the execution of methods accessing properties in the prototype object is faster than methods using private variables in a closure. You can see this for yourself by testing this code:

var instance = new ConstructorPrivateProperties();

for (var i = 0; i < 1000; i++) {
 instance.publicMethod1();
}

vs.

var instance = new PrototypePrivateProperties();

for (var i = 0; i < 1000; i++) {
 instance.publicMethod1();
}

The augmented prototype object version is the fastest now. There’s an interesting jump in Chrome 25. You can follow the thread here to understand exactly how this works.

So are you targeting faster constructors, class definitions or methods? The fact is that any of these three stages can be optimized, but generally with a tradeoff.

Comments
blog comments powered by Disqus