JS Inheritance Performance (v19)

Revision 19 of this benchmark created by DotNetWise on


Description

Testing widely used inheritance libraries against Fiber.js and native javascript

Preparation HTML

<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="//ajax.googleapis.com/ajax/libs/mootools/1.3/mootools-yui-compressed.js">
</script>
<script src="//ajax.googleapis.com/ajax/libs/ext-core/3/ext-core.js">
</script>
<script src="http://kiro.me/temp/fiber.js">
</script>
<script>
  Function.prototype.define = function(name, methods, proto) {
    var ctor = this;
    if (proto) {
      var base = ctor.prototype = new proto();
      ctor.prototype.constructor = ctor;
    }

    var p = ctor.prototype;
    p[name] = p;
    p.ctor = ctor;
    p.m = methods.m;
    //alert(p.m);
    return ctor;
  }
</script>

Setup

(function() {
      var canDefineNonEnumerableProperty = typeof Object.defineProperty === "function";
    
      function __() {};
      Function.prototype.inherit = function(creator, makeConstructorNotEnumerable) {
        /// <summary>Inherits the prototype of the given function. The creator function should return a new Constructor function whose prototype will share this functions's prototype</summary>
        /// <param name="creator" type="Function">function(base, baseConstructor) {<br/>
        ///  return function Derived() {  <br/>
        ///             base.apply(this, arguments); <br/>
        ///     } <br/>
        ///     Notice that there is no .override method call after the function Derrived(){}. This is very important. If you do want to override some methods then use .extend instead
        ///  </param>
        /// <param name="makeConstructorNotEnumerable" type="Boolean">Object.defineProperty is rather slow. If you concern about performance and don't care about 'constructor' being enumerable in for (var i in instance) then let this false. <br/>
        /// Otherwise set it to true and we will redefine the correct constructor i.e. Extendee.prototype.constructor === Extendee that is non-Enumerable
        /// </param>
        __.prototype = this.prototype;
        var Derived = creator.call(this, this.prototype, this);
        Derived.prototype = new __;
        this.__class = Derived.__class = true;
    
        //if we care about Extendee.prototype.constructor === Extendee to be non-Enumerable
        //By default we set the constructor but we don't make it non-enumerable
        if (makeConstructorNotEnumerable && canDefineNonEnumerableProperty) //this is not default as it carries over some performance overhead
        Object.defineProperty(extendeePrototype, 'constructor', {
          enumerable: false,
          value: Derived
        });
        else Derived.prototype.constructor = Derived; //Also fallback for older non-ECMA-5 browsers
        return Derived;
      };
      Function.prototype.extend = function(creator) {
        /// <summary>Extends the prototype of the given function. The creator function should return a new Constructor function whose prototype will share this functions's prototype.</summary>
        /// <param name="creator" type="Function">function(base, baseConstructor) {<br/>
        ///  return function Derived() {  <br/>
        ///             base.apply(this, arguments); <br/>
        ///     }.override(base, {      //custom functions to be added on Derived.prototype <br/>
        ///       method: function() { <br/>
        ///             return base.method.apply(this, arguments); //call the base class' method, assuming is any <br/>
        ///      } <br/>
        ///    }); <br/>
        ///    So it is very important to call the .override at the end. If you simply want to inherit from an object with no overrides then you should call .inherit function instead
        ///  </param>
        var Derived = creator.call(this, this.prototype, this);
    
        this.__class = Derived.__class = true;
    
        return Derived;
      };
    
      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 p in prototype)
        extendeePrototype[p] = prototype[p];
        return this;
      }
    
      Function.prototype.override = function(basePrototype, prototype, makeConstructorNotEnumerable) {
        /// <summary>Extends 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>
        __.prototype = basePrototype;
        var extendeePrototype = new __;
        this.prototype = extendeePrototype;
    
        //if we care about Extendee.prototype.constructor === Extendee to be non-Enumerable
        //By default we set the constructor but we don't make it non-enumerable
        if (makeConstructorNotEnumerable && canDefineNonEnumerableProperty) //this is not default as it carries over some performance overhead
        Object.defineProperty(extendeePrototype, 'constructor', {
          enumerable: false,
          value: this
        });
        else extendeePrototype.constructor = this;
    
    
        if (prototype) for (var p in prototype)
        extendeePrototype[p] = prototype[p];
        return this;
      }
    
      Function.prototype.inheritWith = function(creator, makeConstructorNotEnumerable) {
        /// <summary>Extends 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 baseCtor = this;
        var creatorResult = creator.call(this, this.prototype, this) || {};
        var Derrived = creatorResult.constructor ||
        function defaultCtor() {
          baseCtor.apply(this, arguments);
        }; //automatic constructor if ommited
        alert(Derrived);
        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;
      }
    })();

Test runner

Ready to run.

Testing in
TestOps/sec
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;
  }
});

var B = A.extend({
  init: function(val) {
    this._super(val);
  },
  method1: 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);
  }
});

var a = new A("a");
a.method1("x", "y", "z");

var b = new B("b");
b.method1("y", "z");

var c = new C("c");
c.method1("y");
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;
  }
});

var B = A.extend(function(val) {}).methods({
  method1: function(y, z) {
    this.supr('x', y, z);
  }
});

var C = B.extend(function(val) {}).methods({
  method1: function(z) {
    this.supr('y', z);
  }
});

var a = new A("a");
a.method1("x", "y", "z");

var b = new B("b");
b.method1("y", "z");

var c = new C("c");
c.method1("y");
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;
  }
});

var B = A.$extend({
  __init__: function(val) {
    this.$super(val);
  },
  method1: 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);
  }
});

var a = new A("a");
a.method1("x", "y", "z");

var b = new B("b");
b.method1("y", "z");

var c = new C("c");
c.method1("z");
ready
Backbone.js
var A = Backbone.Model.extend({
  initalize: function(val) {
    this.val = val;
  },
  method1: function(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
  }
});

var B = A.extend({
  method1: function(y, z) {
    A.prototype.method1.call(this, 'x', y, z);
  }
});

var C = B.extend({
  method1: function(z) {
    B.prototype.method1.call(this, 'y', z);
  }
});


var a = new A("a");
a.method1("x", "y", "z");

var b = new B("b");
b.method1("y", "z");

var c = new C("c");
c.method1("y");
ready
MooTools
var A = new Class({
  initialize: function(val) {
    this.val = val;
  },
  method1: function(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
  }
});

var B = new Class({
  Extends: A,
  initialize: function(val) {
    this.parent(val);
  },
  method1: function(y, z) {
    this.parent('x', y, z);
  }
});

var C = new Class({
  Extends: B,
  initialize: function(val) {
    this.parent(val);
  },
  method1: function(z) {
    this.parent('y', z);
  }
});

var a = new A("a");
a.method1("x", "y", "z");

var b = new B("b");
b.method1("y", "z");

var c = new C("c");
c.method1("z");
ready
ExtJS
var A = function(val) {
    this.val = val;
    }
    
    
    
A.prototype.method1 = function(x, y, z) {
  this.x = x;
  this.y = y;
  this.z = z;
};

var B = function(val) {
    B.superclass.constructor.call(this, val);
    }
    
    
    
Ext.extend(B, A, {
  method1: function(y, z) {
    B.superclass.method1.call(this, 'x', y, z);
  }
});

var C = function(val) {
    C.superclass.constructor.call(this, val);
    }
    
    
    
Ext.extend(C, B, {
  method1: function(z) {
    C.superclass.method1.call(this, 'y', z);
  }
});

var a = new A("a");
a.method1("x", "y", "z");

var b = new B("b");
b.method1("y", "z");

var c = new C("c");
c.method1("z");
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;
  }
});

var B = PTClass.create(A, {
  intialize: function($super, val) {
    $super(val);
  },
  method1: 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);
  }
});

var a = new A("a");
a.method1("x", "y", "z");

var b = new B("b");
b.method1("y", "z");

var c = new C("c");
c.method1("c");
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;
    }
  }
});

var B = A.extend(function(base) {
  return {
    method1: function(y, z) {
      base.method1.call(this, 'x', y, z);
    }
  }
});

var C = B.extend(function(base) {
  return {
    method1: function(z) {
      base.method1.call(this, 'y', z);
    }
  }
});

var a = new A("a");
a.method1("x", "y", "z");

var b = new B("b");
b.method1("y", "z");

var c = new C("c");
c.method1("z");
ready
Native
// Native
var A = function(val) {
    if (!val) {
      this.val = val;
    }
    }
    
    
    
A.prototype.method1 = function(x, y, z) {
  this.x = x;
  this.y = y;
  this.x = z;
}

var B = function(val) {
    A.call(this, val);
    }
    
    
    
B.prototype = new A();
B.prototype.constructor = B;
B.prototype.method1 = function(y, z) {
  A.prototype.method1.call(this, 'x', y, z);
}

var C = function(val) {
    B.call(this, val);
    }
    
    
    
C.prototype = new B();
C.prototype.constructor = C;
C.prototype.method1 = function(z) {
  B.prototype.method1.call(this, 'y', z);
};

var a = new A("a");
a.method1("x", "y", "z");

var b = new B("b");
b.method1("y", "z");

var c = new C("c");
c.method1("z");
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;
  };
  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);
  };
  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);
  };
  return C;
})(B);
var a = new A("a");
a.method1("x", "y", "z");
var b = new B("b");
b.method1("y", "z");
var c = new C("c");
c.method1("z");
ready
DotNetWise extend
var A = function(val) {
    if (val) {
      this.val = val;
    }
    }.define({
    method1: function(x, y, z) {
      this.x = x;
      this.y = y;
      this.z = z;
    }
  });
var B = A.extend(function(base, baseCtor) {
  return function B(val) {
    baseCtor.apply(this, arguments);
  }.override({
    method1: function(y, z) {
      base.method1.call(this, 'x', y, z);
    }
  });
});

var C = B.extend(function(base, baseCtor) {
  return function C(val) {
    baseCtor.apply(this, arguments);
  }.override(base, {
    method1: function(z) {
      base.method1.call(this, 'y', z);
    }
  });
});

var a = new A("a");
a.method1("x", "y", "z");
var b = new B("b");
b.method1("y", "z");
var c = new C("c");
c.method1("z");
ready
DotNetWise extend - fix constructor
var A = function(val) {
    if (val) {
      this.val = val;
    }
    }.define({
    method1: function(x, y, z) {
      this.x = x;
      this.y = y;
      this.z = z;
    }
  });
var B = A.extend(function(base, baseCtor) {
  return function B(val) {
    baseCtor.apply(this, arguments);
  }.override({
    method1: function(y, z) {
      base.method1.call(this, 'x', y, z);
    }
  }, true);
});

var C = B.extend(function(base, baseCtor) {
  return function C(val) {
    baseCtor.apply(this, arguments);
  }.override(base, {
    method1: function(z) {
      base.method1.call(this, 'y', z);
    }
  }, true);
});

var a = new A("a");
a.method1("x", "y", "z");
var b = new B("b");
b.method1("y", "z");
var c = new C("c");
c.method1("z");
ready
DotNetWise inheritWith
var A = function(val) {
    if (val) {
      this.val = val;
    }
    }.define({
    method1: function(x, y, z) {
      this.x = x;
      this.y = y;
      this.z = z;
    }
  });
var B = A.inheritWith(function(base, baseCtor) {
  return {
    constructor: function(val) {
      baseCtor.apply(this, arguments);
    },
    method1: function(y, z) {
      base.method1.call(this, 'x', y, z);
    }
  }
});

var C = B.inheritWith(function(base, baseCtor) {
  return {
    constructor: function C(val) {
      baseCtor.apply(this, arguments);
    },
    method1: function(z) {
      base.method1.call(this, 'y', z);
    }
  }
});

var a = new A("a");
a.method1("x", "y", "z");
var b = new B("b");
b.method1("y", "z");
var c = new C("c");
c.method1("z");
ready

Revisions

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