JS Inheritance Performance

JavaScript performance comparison

Revision 40 of this test case created by Denes Pal

Info

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

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 code

<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>
<script>
Benchmark.prototype.setup = function() {
    ///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");
      }
    }
};
</script>

Preparation code output

Test runner

Warning! For accurate results, please disable Firebug before running the tests. (Why?)

Java applet disabled.

Testing in unknown unknown
Test Ops/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();
pending…
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();
pending…
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();
pending…
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();
pending…
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();
pending…
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();
pending…
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();
pending…
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();
pending…
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();
pending…
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();
pending…
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();
pending…
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();
pending…
Inheritance
var A = function(input) {

  this.val = input;

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

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

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

var BClass = function(parent) {

};

var B = function(input) {
  return new A(input).factory(BClass);
};

var CClass = function(parent) {

};

var C = function(input) {
  return new B(input).factory(CClass);
};
pending…

Compare results of other browsers

Revisions

You can edit these tests or add even more tests to this page by appending /edit to the URL. Here’s a list of current revisions for this page:

1 comment

Denes Pal commented :

Unit tests: http://jsbin.com/ewatit/37/edit

Feedbacks are welcome!

Add a comment