JS Inheritance Performance

JavaScript performance comparison

Revision 26 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 methods from version 25 were removed as they were way too weak! To compare against them please go here http://jsperf.com/js-inheritance-performance/25

However, look at numbers for the above! What a difference! All 'weak' libraries carried a lot of junk with them and made even the highest methods to perform about half! (Chrome 26 at least)

Preparation code

<script src="http://code.jquery.com/jquery-1.9.1.min.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.selfExtend = function(creator) {
    var ctor = creator(this.prototype, this);
    ctor.prototype = this.prototype;
    var c = new ctor();
    c.constructor.prototype = c;
    return c.constructor;
  };
</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 100 instances of A, B and C and calls .method1 on them
    function RunTests() {
      for (var i = 0; i < 100; 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
Native
// Native - although theoretically fastest, most ugly/unusable b/c of too much repetitive code
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…
ExtJS
//Almost fast enough, but you need to embed Ext Core (30KB gzipped and minified is crazy!)
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…
Fiber.js
//Nice syntax, but slowest
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…
TypeScript
//Lucky you if you can do TypeScript, otherwise this is useless (too ugly to manually write)
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…
DotNetWise extend
//Faster than Fiber.js but still way behind Native
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…
DotNetWise extend - fix constructor
//Same as above, makes 'constructor' not enumerable with Object.defineProperty which makes it even slower!
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…
DotNetWise inheritWith
//Also similar with Fiber.js, somewhat faster (especially and firefox) and even better syntax

var A = function(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.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);
    }
  }
});

for (var i = 0; i < 100; 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");
}
pending…
DNW FastClass
//Fork me on GitHub! https://github.com/dotnetwise/Javascript-FastClass
//Almost fast as TypeScript version, even faster than it on some browsers
var A = AA();
var B = A.selfExtend(function(base, baseCtor) {
  return function() { //basically this function returns what's supposed to be below constructor's prototype - hence we're only relying on Javascript prototype inheritance at any given time
    this.constructor = function(val) {
      baseCtor.call(this, val);
    };
    this.method1 = function(y, z) {
      base.method1.call(this, 'x', y, z);
    }
  };
});
var C = B.selfExtend(function(base, baseCtor) {
  return function ctor() {
    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