JS Inheritance Performance (v60)

Revision 60 of this benchmark created on


Description

Testing widely used inheritance libraries against TypeScript (as a reference point),

  • Measuring Using classes performance (without benchmarking the code for the libraries themselves).
  • For testing Declaring classes performance of the most rapid libraries please see this: http://jsperf.com/js-inheritance-performance/34
  • DNW .fastClass
  • DNW .inheritWith / DNW .inheritWith2
  • Native
  • TypeScript (as reference)
  • Fiber.js
  • PTClass (Prototype.js)
  • Classy
  • Klass
  • John Resig's Class
  • Backbone.js

Benchmark for the top most rapid libraries: http://jsperf.com/js-inheritance-performance/35

MooTools and Ext Core are removed because they add extra information into native classes. They slow down other libraries. Ext Core OOP is fast (about 15-25% slower than native), MooTools OOP is super slow!

Preparation HTML

<script>
(function(){
        var initializing = false;
        // The base Class implementation (does nothing)
        this.FastClass = function(){};

        // Create a new Class that inherits from this class
        FastClass.extend = function(prop) {
                var _super = this.prototype;

                // Instantiate a base class (but only create the instance,
                // don't run the init constructor)
                initializing = true;
                var prototype = new this();
                initializing = false;


                for (name in prop) {
                        value = prop[name];
                        currentValue = prototype[name];
                        if (typeof value === 'function' && typeof currentValue === 'function' &&
                                value !== currentValue) {
                                value.base = currentValue;
                        } else {
                                prototype[name] = prop[name];
                        }
                }



                // The dummy class constructor
                function FastClass() {
                        // All construction is actually done in the init method
                        if ( !initializing && this.init )
                                this.init.apply(this, arguments);
                }

                // Populate our constructed prototype object
                FastClass.prototype = prototype;

                // Enforce the constructor to be what we expect
                FastClass.constructor = FastClass;

                // And make this class extendable
                FastClass.extend = arguments.callee;

                return FastClass;
        };
})();
</script>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="http://dl.dropbox.com/u/7677927/oop-benchmark/lib/jrclass.js"></script>
<script src="http://dl.dropbox.com/u/7677927/oop-benchmark/lib/klass.js"></script>
<script src="http://dl.dropbox.com/u/7677927/oop-benchmark/lib/classy.js"></script>
<script src="http://dl.dropbox.com/u/7677927/oop-benchmark/lib/ptclass.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.3.1/underscore-min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/backbone.js/0.9.0/backbone-min.js"></script>
<script src="http://kiro.me/temp/fiber.js"></script>
<script>
  //DNW.FastClass - fork me on GitHub https://github.com/dotnetwise/Javascript-FastClass
  (function() {
    var Object_keys = Object.keys;
    var Object_defineProperty = Object.defineProperty;

    var canDefineNonEnumerableProperty = typeof Object_defineProperty === "function";
    var supportsProto = {};
    supportsProto = supportsProto.__proto__ === Object.prototype;
    if (supportsProto) {
      try {
        supportsProto = {};
        supportsProto.__proto__ = {
          Object: 1
        };
        supportsProto = supportsProto.Object === 1; //setting __proto__ in firefox is terribly slow!
      } catch (ex) {
        supportsProto = false;
      }
    }

    function __() {};
    Function.prototype.fastClass = function(creator, makeConstructorNotEnumerable) {
      /// <summary>Inherits the function's prototype to a new function named constructor returned by the creator parameter</summary>
      /// <param name="creator" type="function(base, baseCtor) { }">where base is BaseClass.prototype and baseCtor is BaseClass - aka the function you are calling .fastClass on</param>
      //this == constructor of the base "Class"
      var baseClass = this;
      var base = this.prototype;
      creator = creator ||
      function() {
        this.constructor = function() {
          baseClass.apply(this, arguments);
        }
      };
      creator.prototype = base;

      //creating the derrived class' prototype
      var derrivedProrotype = new creator(base, this);

      //did you forget or not intend to add a constructor? We'll add one for you
      if (!derrivedProrotype.hasOwnProperty("constructor")) derrivedProrotype.constructor = function() {
        baseClass.apply(this, arguments);
      }

      //By default we set the constructor but we don't make it non-enumerable
      //if we care about constructor.prototype.constructor === constructor to be non-Enumerable we need to use Object.defineProperty
      if (makeConstructorNotEnumerable && canDefineNonEnumerableProperty) //this is not default as it carries over some performance overhead
      Object_defineProperty(prototype, 'constructor', {
        enumerable: false,
        value: Derrived
      });

      //setting the derrivedPrototype to constructor's prototype
      derrivedProrotype.constructor.prototype = derrivedProrotype;

      //returning the constructor
      return derrivedProrotype.constructor;
    };

    Function.prototype.inheritWith = function(creator, makeConstructorNotEnumerable) {
      /// <summary>Inherits the function's prototype to a new function named constructor returned by the creator parameter</summary>
      /// <param name="creator" type="function(base, baseCtor) { return { constructor: function() {..}...} }">where base is BaseClass.prototype and baseCtor is BaseClass - aka the function you are calling .inheritWith on</param>
      var baseCtor = this;
      var creatorResult = creator.call(this, this.prototype, this) || {};
      var Derrived = creatorResult.constructor ||
      function defaultCtor() {
        baseCtor.apply(this, arguments);
      }; //automatic constructor if ommited
      var derrivedPrototype;
      __.prototype = this.prototype;
      Derrived.prototype = derrivedPrototype = new __;

      for (var p in creatorResult)
      derrivedPrototype[p] = creatorResult[p];

      //By default we set the constructor but we don't make it non-enumerable
      //if we care about Derrived.prototype.constructor === Derrived to be non-Enumerable we need to use Object.defineProperty
      if (makeConstructorNotEnumerable && canDefineNonEnumerableProperty) //this is not default as it carries over some performance overhead
      Object_defineProperty(derrivedPrototype, 'constructor', {
        enumerable: false,
        value: Derrived
      });

      return Derrived;
    };


    Function.prototype.inheritWith2 = !supportsProto ? Function.prototype.inheritWith : function(creator, makeConstructorNotEnumerable) {
      /// <summary>Inherits the function's prototype to a new function named constructor returned by the creator parameter</summary>
      /// <param name="creator" type="function(base, baseCtor) { return { constructor: function() {..}...} }">where base is BaseClass.prototype and baseCtor is BaseClass - aka the function you are calling .inheritWith on</param>
      var baseCtor = this;
      var derrivedPrototype = creator.call(this, this.prototype, this) || {};
      var Derrived = derrivedPrototype.constructor ||
      function defaultCtor() {
        baseCtor.apply(this, arguments);
      }; //automatic constructor if ommited
      Derrived.prototype = derrivedPrototype;
      derrivedPrototype.__proto__ = this.prototype;

      //By default we set the constructor but we don't make it non-enumerable
      //if we care about Derrived.prototype.constructor === Derrived to be non-Enumerable we need to use Object.defineProperty
      if (makeConstructorNotEnumerable && canDefineNonEnumerableProperty) //this is not default as it carries over some performance overhead
      Object_defineProperty(derrivedPrototype, 'constructor', {
        enumerable: false,
        value: Derrived
      });

      return Derrived;
    };


    Function.prototype.define = function(prototype) {
      /// <summary>Define members on the prototype of the given function with the custom methods and fields specified in the prototype parameter.</summary>
      /// <param name="prototype" type="Plain Object">A custom object with the methods or properties to be added on Extendee.prototype</param>
      var extendeePrototype = this.prototype;

      if (prototype) {
        for (var key in prototype)
        extendeePrototype[key] = prototype[key];
      }
      return this;
    }


    Function.define = function(creator, makeConstructorNotEnumerable) {
      /// <summary>Defines a function named constructor returned by the creator parameter and extends it's protoype with all other functions</summary>
      /// <param name="creator" type="function() { return { constructor: function() {..}...} }"></param>
      var creatorResult = creator.call(this) || {};
      var constructor = creatorResult.constructor ||
      function() {}; //automatic constructor if ommited
      var prototype = constructor.prototype;
      for (var p in creatorResult)
      prototype[p] = creatorResult[p];

      //By default we set the constructor but we don't make it non-enumerable
      //if we care about constructor.prototype.constructor === constructor to be non-Enumerable we need to use Object.defineProperty
      if (makeConstructorNotEnumerable && canDefineNonEnumerableProperty) //this is not default as it carries over some performance overhead
      Object_defineProperty(prototype, 'constructor', {
        enumerable: false,
        value: Derrived
      });

      return constructor;
    };

  })();
</script>
<script type="text/javascript">
  var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
  document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">
  try {
    var pageTracker = _gat._getTracker("UA-11643574-1");
    pageTracker._trackPageview();
  } catch (err) {}
</script>

Setup

///Define ctor A
    
    function AA() {
      function A(val) {
        this.val = val;
      };
    
      A.prototype.method1 = function(x, y, z) {
        this.x = x;
        this.y = y;
        this.x = z;
      };
      A.prototype.method2 = function(x, y, z) {
        this.x = x;
        this.y = y;
        this.x = z;
      };
      A.prototype.method3 = function(x, y, z) {
        this.x = x;
        this.y = y;
        this.x = z;
      };
      return A;
    }
    
    //Creates 500 instances of A, B and C and calls .method1, .method2 and .method3 on them
    
    function RunTests() {
      for (i = 500; i--;) {
        var a = new A("a");
        a.method1("x", "y", "z");
        a.method2("x", "y", "z");
        a.method3("x", "y", "z");
        var b = new B("b");
        b.method1("y", "z");
        b.method2("y", "z");
        b.method3("y", "z");
        var c = new C("c");
        c.method1("z");
        c.method2("z");
        c.method3("z");
      }
    }

Test runner

Ready to run.

Testing in
TestOps/sec
DNW inheritWith
//Fork me on GitHub! https://github.com/dotnetwise/Javascript-FastClass
//creator is the function that returns the custom methods in the new prototype. 
var A = AA();
var B = A.inheritWith(function(base, baseCtor) {
  return {
    constructor: function(val) {
      baseCtor.call(this, val);
    },
    method1: function(y, z) {
      base.method1.call(this, 'x', y, z);
    },
    method2: function(y, z) {
      base.method2.call(this, 'x', y, z);
    },
    method3: function(y, z) {
      base.method3.call(this, 'x', y, z);
    }
  }
});

var C = B.inheritWith(function(base, baseCtor) {
  return {
    constructor: function C(val) {
      baseCtor.call(this, val);
    },
    method1: function(z) {
      base.method1.call(this, 'y', z);
    },
    method2: function(z) {
      base.method2.call(this, 'y', z);
    },
    method3: function(z) {
      base.method3.call(this, 'y', z);
    }
  }
});
RunTests();
ready
TypeScript
var __extends = this.__extends ||
function(d, b) {
  function __() {
    this.constructor = d;
  }
  __.prototype = b.prototype;
  d.prototype = new __();
};
var A = (function() {
  function A(val) {
    this.val = val;
  }
  A.prototype.method1 = function(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
  };
  A.prototype.method2 = function(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
  };
  A.prototype.method3 = function(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
  };
  return A;
})();
var B = (function(_super) {
  __extends(B, _super);

  function B(val) {
    _super.call(this, val);
  }
  B.prototype.method1 = function(y, z) {
    _super.prototype.method1.call(this, 'x', y, z);
  };
  B.prototype.method2 = function(y, z) {
    _super.prototype.method2.call(this, 'x', y, z);
  };
  B.prototype.method3 = function(y, z) {
    _super.prototype.method3.call(this, 'x', y, z);
  };
  return B;
})(A);
var C = (function(_super) {
  __extends(C, _super);

  function C(val) {
    _super.call(this, val);
  }
  C.prototype.method1 = function(z) {
    _super.prototype.method1.call(this, 'y', z);
  };
  C.prototype.method2 = function(z) {
    _super.prototype.method2.call(this, 'y', z);
  };
  C.prototype.method3 = function(z) {
    _super.prototype.method3.call(this, 'y', z);
  };
  return C;
})(B);
RunTests();
ready
Native
// Native
var A = AA();
var B = function(val) {
    A.call(this, val);
    };

function __() {};
__.prototype = A.prototype;
B.prototype = new __();
B.prototype.constructor = B;
B.prototype.method1 = function(y, z) {
  A.prototype.method1.call(this, 'x', y, z);
};
B.prototype.method2 = function(y, z) {
  A.prototype.method2.call(this, 'x', y, z);
};
B.prototype.method3 = function(y, z) {
  A.prototype.method3.call(this, 'x', y, z);
};
var C = function(val) {
    B.call(this, val);
    };
__.prototype = B.prototype;
C.prototype = new __();
C.prototype.constructor = C;
C.prototype.method1 = function(z) {
  B.prototype.method1.call(this, 'y', z);
};
C.prototype.method2 = function(z) {
  B.prototype.method2.call(this, 'y', z);
};
C.prototype.method3 = function(z) {
  B.prototype.method3.call(this, 'y', z);
};
RunTests();
ready
DNW FastClass
//Fork me on GitHub! https://github.com/dotnetwise/Javascript-FastClass
//creator is the function that returns the custom methods in the new prototype. 
// sets `function(base, baseCtor).prototype = base` before calling the function. We append new prototype members to this, one by one
var A = AA();
var B = A.fastClass(function(base, baseCtor) {
  this.constructor = function(val) {
    baseCtor.call(this, val);
  };
  this.method1 = function(y, z) {
    base.method1.call(this, 'x', y, z);
  };
  this.method2 = function(y, z) {
    base.method2.call(this, 'x', y, z);
  };
  this.method3 = function(y, z) {
    base.method3.call(this, 'x', y, z);
  };
});
var C = B.fastClass(function(base, baseCtor) {
  this.constructor = function(val) {
    baseCtor.call(this, val);
  };
  this.method1 = function(z) {
    base.method1.call(this, 'y', z);
  };
  this.method2 = function(z) {
    base.method2.call(this, 'y', z);
  };
  this.method3 = function(z) {
    base.method3.call(this, 'y', z);
  };
});
RunTests();
ready
DNW inheritWith2
//Fork me on GitHub! https://github.com/dotnetwise/Javascript-FastClass
//creator is the function that returns the custom methods in the new prototype.
// Sets the returned object {}.__proto__ = base. Not all the browsers support this (see IE<=10) For that matter we'll fallback to inheritWith on IE! So IE tests for inheritWith2 are irrelvant.
var A = AA();
var B = A.inheritWith2(function(base, baseCtor) {
  return {
    constructor: function(val) {
      baseCtor.call(this, val);
    },
    method1: function(y, z) {
      base.method1.call(this, 'x', y, z);
    },
    method2: function(y, z) {
      base.method2.call(this, 'x', y, z);
    },
    method3: function(y, z) {
      base.method3.call(this, 'x', y, z);
    }
  }
});

var C = B.inheritWith2(function(base, baseCtor) {
  return {
    constructor: function C(val) {
      baseCtor.call(this, val);
    },
    method1: function(z) {
      base.method1.call(this, 'y', z);
    },
    method2: function(z) {
      base.method2.call(this, 'y', z);
    },
    method3: function(z) {
      base.method3.call(this, 'y', z);
    }
  }
});
RunTests();
ready
John Resig's Class
var A = JRClass.extend({
  init: function(val) {
    this.val = val;
  },
  method1: function(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
  },
  method2: function(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
  },
  method3: function(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
  }
});
var B = A.extend({
  init: function(val) {
    this._super(val);
  },
  method1: function(y, z) {
    this._super('x', y, z);
  },
  method2: function(y, z) {
    this._super('x', y, z);
  },
  method3: function(y, z) {
    this._super('x', y, z);
  }
});
var C = B.extend({
  init: function(val) {
    this._super(val);
  },
  method1: function(z) {
    this._super('y', z);
  },
  method2: function(z) {
    this._super('y', z);
  },
  method3: function(z) {
    this._super('y', z);
  }
});
RunTests();
ready
Klass
var A = klass(function(val) {
  this.val = val;
}).methods({
  method1: function(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
  },
  method2: function(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
  },
  method3: function(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
  }
});
var B = A.extend(function(val) {}).methods({
  method1: function(y, z) {
    this.supr('x', y, z);
  },
  method2: function(y, z) {
    this.supr('x', y, z);
  },
  method3: function(y, z) {
    this.supr('x', y, z);
  }
});
var C = B.extend(function(val) {}).methods({
  method1: function(z) {
    this.supr('y', z);
  },
  method2: function(z) {
    this.supr('y', z);
  },
  method3: function(z) {
    this.supr('y', z);
  }
});
RunTests();
ready
Classy
var A = Classy.$extend({
  __init__: function(val) {
    this.val = val;
  },
  method1: function(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
  },
  method2: function(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
  },
  method3: function(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
  }
});
var B = A.$extend({
  __init__: function(val) {
    this.$super(val);
  },
  method1: function(y, z) {
    this.$super('x', y, z);
  },
  method2: function(y, z) {
    this.$super('x', y, z);
  },
  method3: function(y, z) {
    this.$super('x', y, z);
  }
});
var C = B.$extend({
  __init__: function(val) {
    this.$super(val);
  },
  method1: function(z) {
    this.$super('y', z);
  },
  method2: function(z) {
    this.$super('y', z);
  },
  method3: function(z) {
    this.$super('y', z);
  }
});
RunTests();
ready
Backbone.js
var A = Backbone.Model.extend({
  constructor: function(val) {
    this.val = val;
    Backbone.Model.apply(this, arguments);
  },
  method1: function(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
  },
  method2: function(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
  },
  method3: function(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
  }
});
var B = A.extend({
  constructor: function(val) {
    A.apply(this, arguments);
  },
  method1: function(y, z) {
    A.prototype.method1.call(this, 'x', y, z);
  },
  method2: function(y, z) {
    A.prototype.method2.call(this, 'x', y, z);
  },
  method3: function(y, z) {
    A.prototype.method3.call(this, 'x', y, z);
  }
});
var C = B.extend({
  constructor: function(val) {
    B.apply(this, arguments);
  },
  method1: function(z) {
    B.prototype.method1.call(this, 'y', z);
  },
  method2: function(z) {
    B.prototype.method2.call(this, 'y', z);
  },
  method3: function(z) {
    B.prototype.method3.call(this, 'y', z);
  }
});
RunTests();
ready
PTClass (Prototype.js)
var A = PTClass.create({
  intialize: function(val) {
    this.val = val;
  },
  method1: function(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
  },
  method2: function(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
  },
  method3: function(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
  }
});

var B = PTClass.create(A, {
  intialize: function($super, val) {
    $super(val);
  },
  method1: function($super, y, z) {
    $super('x', y, z);
  },
  method2: function($super, y, z) {
    $super('x', y, z);
  },
  method3: function($super, y, z) {
    $super('x', y, z);
  }
});

var C = PTClass.create(B, {
  intialize: function($super, val) {
    $super(val);
  },
  method1: function($super, z) {
    $super('y', z);
  },
  method2: function($super, z) {
    $super('y', z);
  },
  method3: function($super, z) {
    $super('y', z);
  }
});
RunTests();
ready
Fiber.js
var A = Fiber.extend(function() {
  return {
    init: function(val) {
      this.val = val;
    },
    method1: function(x, y, z) {
      this.x = x;
      this.y = y;
      this.z = z;
    },
    method2: function(x, y, z) {
      this.x = x;
      this.y = y;
      this.z = z;
    },
    method3: function(x, y, z) {
      this.x = x;
      this.y = y;
      this.z = z;
    }
  }
});
var B = A.extend(function(base) {
  return {
    init: function(val) {
      base.init.call(this, val);
    },
    method1: function(y, z) {
      base.method1.call(this, 'x', y, z);
    },
    method2: function(y, z) {
      base.method2.call(this, 'x', y, z);
    },
    method3: function(y, z) {
      base.method3.call(this, 'x', y, z);
    }
  }
});
var C = B.extend(function(base) {
  return {
    init: function(val) {
      base.init.call(this, val);
    },
    method1: function(z) {
      base.method1.call(this, 'y', z);
    },
    method2: function(z) {
      base.method2.call(this, 'y', z);
    },
    method3: function(z) {
      base.method3.call(this, 'y', z);
    }
  }
});
RunTests();
ready
CoffeScript
var A, B, C,
  __hasProp = {}.hasOwnProperty,
  __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };

A = (function() {

  function A(val) {
    this.val = val;
  }

  A.prototype.method1 = function(x, y, z) {
    this.x = x;
    this.y = y;
    return this.z = z;
  };

  A.prototype.method2 = function(x, y, z) {
    this.x = x;
    this.y = y;
    return this.z = z;
  };

  A.prototype.method3 = function(x, y, z) {
    this.x = x;
    this.y = y;
    return this.z = z;
  };

  return A;

})();

B = (function(_super) {

  __extends(B, _super);

  function B() {
    return B.__super__.constructor.apply(this, arguments);
  }

  B.prototype.method1 = function(y, z) {
    return B.__super__.method1.call(this, 'x', y, z);
  };

  B.prototype.method2 = function(x, y, z) {
    return B.__super__.method2.call(this, 'x', y, z);
  };

  B.prototype.method3 = function(x, y, z) {
    return B.__super__.method3.call(this, 'x', y, z);
  };

  return B;

})(A);

C = (function(_super) {

  __extends(C, _super);

  function C() {
    return C.__super__.constructor.apply(this, arguments);
  }

  C.prototype.method1 = function(z) {
    return C.__super__.method1.call(this, 'y', z);
  };

  C.prototype.method2 = function(z) {
    return C.__super__.method2.call(this, 'y', z);
  };

  C.prototype.method3 = function(z) {
    return C.__super__.method3.call(this, 'y', z);
  };

  return C;

})(B);
RunTests();
ready
FastClass
var A = FastClass.extend({
  init: function(val) {
    this.val = val;
  },
  method1: function(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
  },
  method2: function(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
  },
  method3: function(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
  }
});
var B = A.extend({
  init: function(val) {
    init.base.call(val);
  },
  method1: function(y, z) {
    method1.base.call('x', y, z);
  },
  method2: function(y, z) {
    method2.base.call('x', y, z);
  },
  method3: function(y, z) {
    method3.base.call('x', y, z);
  }
});
var C = B.extend({
  init: function(val) {
    init.base.call(val);
  },
  method1: function(z) {
    method1.base.call('y', z);
  },
  method2: function(z) {
    method2.base.call('y', z);
  },
  method3: function(z) {
    method3.base.call('y', z);
  }
});
RunTests();
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.