JS Inheritance Performance

JavaScript performance comparison

Revision 30 of this test case created by DotNetWise

Info

Testing widely used inheritance libraries against Fiber.js, ExtJS, TypeScript, DNW FastClass and Native javascript All other libraries are either previous variances of DNW FastClass or are way too weak to compete with the above ones.

Preparation code

<script src="//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js">
</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="https://raw.github.com/ded/klass/master/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="https://raw.github.com/documentcloud/underscore/master/underscore.js"></script>
<script src="https://raw.github.com/documentcloud/backbone/master/backbone.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>
  //DNW.FastClass - fork me on GitHub https://github.com/dotnetwise/Javascript-FastClass
  Function.prototype.fastClass = function(creator) {
    var base = this.prototype;
    creator.prototype = base;
    var newProto = new creator(base, this);
    newProto.hasOwnProperty('constructor') || (newProto.constructor = function() {
      base.apply(this, arguments);
    });
    newProto.constructor.prototype = newProto;
    return newProto.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() {
    (function() {
      //old variations of DNW.FastClass
      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
        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;
      };
   
    })();
   
    ///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;
      };
      return A;
    }
   
    //Creates 500 instances of A, B and C and calls .method1 on them
   
    function RunTests() {
      for (var i = 0; i < 500; i++) {
        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");
      }
    }
};
</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
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);
  }
});
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;
  }
});
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);
  }
});
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;
  }
});
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);
  }
});
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;
  }
});
var B = A.extend({
  constructor: function(val) {
    A.apply(this, arguments);
  },
  method1: function(y, z) {
    A.prototype.method1.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);
  }
});
RunTests();
pending…
ExtJS
var A = AA();
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);
  }
});
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;
  }
});

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);
  }
});
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;
    }
  }
});
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);
    }
  }
});
var C = B.extend(function(base) {
  return {
    init: function(val) {
      base.init.call(this, val);
    },
    method1: function(z) {
      base.method1.call(this, 'y', z);
    }
  }
});
RunTests();
pending…
DNW extend
var A = AA();
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);
    }
  });
});
RunTests();
pending…
DNW extend - fixctor
var A = AA();
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);
});
RunTests();
pending…
DNW inheritWith
/*var A = function(val) {
    this.val = val;
    }.define({
    method1: function(x, y, z) {
      this.x = x;
      this.y = y;
      this.z = z;
    }
  });*/

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);
    }
  }
});

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);
    }
  }
});

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;
  };
  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);
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);
};

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);
};
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. They need to be 'merged' into 'this'
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);
  }
});
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);
  }
});
RunTests();
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:

0 comments

Add a comment