JS Classes & Objects: Definition (v2)

Revision 2 of this benchmark created by Kyle Simpson on


Description

A set of performance tests comparing various ways of hooking up prototype-linked classes & objects.

This test measures the performance of definition of the classes.

Preparation HTML

<script>

// FROM: https://gist.github.com/4289220
function type1(prototype, declaration) {
    function mixin(receiver, supplier) {

        if (Object.getOwnPropertyDescriptor) {

            Object.keys(supplier).forEach(function(property) {
                var descriptor = Object.getOwnPropertyDescriptor(supplier, property);
                Object.defineProperty(receiver, property, descriptor);
            });

        } else {

            for (var property in supplier) {
                if (supplier.hasOwnProperty(property)) {
                    receiver[property] = supplier[property]
                }
            }
        }

        return receiver;
    }

    // if there's only one argument, then the first argument is the declaration
    if (!declaration) {
        declaration = prototype;
        declaration.constructor.prototype = declaration;
    } else {

        // make sure the prototype is an object
        prototype = (typeof prototype == "function") ? prototype.prototype : prototype;

        // create a new prototype for the constructor function
        declaration.constructor.prototype = Object.create(prototype, {
            constructor: {
                configurable: true,
                enumerable: true,
                value: declaration.constructor,
                writable: true
            }
        });

        // add everything from the declaration onto the new prototype
        mixin(declaration.constructor.prototype, declaration);
    }

    // return the now-complete constructor function
    return declaration.constructor;
}


// FROM: https://gist.github.com/4289270
function type2(prototype, declaration) {
   function mixin(receiver, supplier) {
      if (Object.getOwnPropertyDescriptor) {
         Object.keys(supplier).forEach(function(property) {
            var descriptor = Object.getOwnPropertyDescriptor(supplier, property);
            Object.defineProperty(receiver, property, descriptor);
         });
      }
      else {
         for (var property in supplier) {
            if (supplier.hasOwnProperty(property)) {
               receiver[property] = supplier[property]
            }
         }
      }
      return receiver;
   }

   // if `prototype` not specified, default: `Object.prototype`
   if (declaration == null) {
      declaration = prototype;
      prototype = Object.prototype;
   }

   // make sure the prototype is an object
   prototype = (typeof prototype == "function") ? prototype.prototype : prototype;

   // create a new prototype chain link to represent declaration
   var new_prototype = Object.create(prototype);
   mixin(new_prototype,declaration);

   // build the constructor for this new "type"
   var construct = function() {
      // build the new instance, that "inherits" from the prototype chain
      var obj = Object.create(new_prototype,{
         // pretend this instance was created by this constructor function
         constructor: {
            configurable: true,
            enumerable: true,
            value: construct,
            writable: true
         }
      });

      // if a `constructor` was defined for this type, call it
      // if not, call the super `constructor` (if any) automatically
      (declaration.hasOwnProperty("constructor")?declaration.constructor:function(){
         (prototype.hasOwnProperty("constructor")?prototype.constructor:function(){}).apply(this,arguments);
      }).apply(obj,arguments);

      return obj;
   };

   // pretend this constructor is actually used to build instances
   // that "inherit" from the specified prototype chain
   construct.prototype = new_prototype;

   return construct;
}


// FROM: https://gist.github.com/4302554
function make3(delegateTo, declaration) {
        function mixin(receiver, supplier) {
                if (Object.getOwnPropertyDescriptor) {
                        Object.keys(supplier).forEach(function(property) {
                                var descriptor = Object.getOwnPropertyDescriptor(supplier, property);
                                Object.defineProperty(receiver, property, descriptor);
                        });
                }
                else {
                        for (var property in supplier) {
                                if (supplier.hasOwnProperty(property)) {
                                        receiver[property] = supplier[property]
                                }
                        }
                }
                return receiver;
        }

        // if `delegateTo` not specified, default: `Object.prototype`
        if (!delegateTo) {
                delegateTo = Object.prototype;
        }

        // make sure the `delegateTo` is an object
        delegateTo = (typeof delegateTo == "function") ? delegateTo.prototype : delegateTo;

        // link the new object to the `delegateTo` object
        var obj = Object.create(delegateTo);

        // define the new object according to the declaration (if any)
        if (declaration) {
                mixin(obj,declaration||{});
        }

        // if no `init` was declared for this new object, but `delegateTo.init()` exists,
        // hook up to it automatically
        if (!obj.hasOwnProperty("init") && delegateTo && delegateTo.hasOwnProperty("init")) {
                obj.init = function(){ return delegateTo.init.apply(this,arguments); };
        }

        return obj;
}

// FROM: https://gist.github.com/4315870
function make4(copyFrom, declaration) {
        function mixin(receiver, supplier) {
                if (Object.getOwnPropertyDescriptor) {
                        Object.keys(supplier).forEach(function(property) {
                                var descriptor = Object.getOwnPropertyDescriptor(supplier, property);
                                Object.defineProperty(receiver, property, descriptor);
                        });
                }
                else {
                        for (var property in supplier) {
                                if (supplier.hasOwnProperty(property)) {
                                        receiver[property] = supplier[property]
                                }
                        }
                }
                return receiver;
        }

        var obj = {};

        // mixin the `copyFrom` object, if any
        if (copyFrom) {
                // make sure the `copyFrom` is an object
                copyFrom = (typeof copyFrom == "function") ? copyFrom.prototype : copyFrom;

                // copy the `copyFrom` object
                mixin(obj,copyFrom);
        }

        // mixin the new `declaration` object, if any
        if (declaration) {
                mixin(obj,declaration);
        }

        return obj;
}

</script>

Setup

var Rectangle1, Square1, Triangle1,
        Rectangle2, Square2, Triangle2,
        Rectangle3, Square3, Triangle3,
        Rectangle4, Square4, Triangle4;

Test runner

Ready to run.

Testing in
TestOps/sec
type1
Rectangle1 = type1({
        constructor: function(length, width) {
                this.length = length;
                this.width = width;
        },
        id: "r1" + Math.random(),
        getArea: function() {
                return this.length * this.width;
        }
});

// inherit from rectangle
Square1 = type1(Rectangle1, {
        constructor: function(size) {
                Rectangle1.call(this, size, size);
        },
        id: "s1" + Math.random()
});

// also inherit from rectangle
Triangle1 = type1(Rectangle1,{
        constructor: function(length, width) {
                Rectangle1.call(this, length, width);
        },
        id: "t1" + Math.random(),
        getArea: function() {
                return 0.5 * this.length * this.width;
        }
});
ready
type2
Rectangle2 = type2({
        constructor: function(length, width) {
                this.length = length;
                this.width = width;
        },
        id: "r2" + Math.random(),
        getArea: function() {
                return this.length * this.width;
        }
});

// inherit from rectangle
Square2 = type2(Rectangle2, {
        constructor: function(size) {
                Rectangle2.prototype.constructor.call(this, size, size);
        },
        id: "s2" + Math.random()
});

// also inherit from rectangle
Triangle2 = type2(Rectangle2,{
        // constructor from Rectangle2 automagically called here!
        id: "t2" + Math.random(),
        getArea: function() {
                return 0.5 * this.length * this.width;
        }
});
ready
make3
Rectangle3 = {
        init: function(length, width) {
                this.length = length;
                this.width = width;
        },
        id: "r3" + Math.random(),
        getArea: function() {
                return this.length * this.width;
        }
};

// inherit from rectangle
Square3 = make3(Rectangle3,{
        init: function(size) {
                Rectangle3.init.call(this, size, size);
        },
        id: "s3" + Math.random()
});

// also inherit from rectangle
Triangle3 = make3(Rectangle3,{
        id: "t3" + Math.random(),
        getArea: function() {
                return 0.5 * this.length * this.width;
        }
});
ready
make4 (mixin baseline)
Rectangle4 = {
        init: function(length, width) {
                this.length = length;
                this.width = width;
        },
        id: "r4" + Math.random(),
        getArea: function() {
                return this.length * this.width;
        }
};

// copy from rectangle
Square4 = make4(Rectangle4,{
        init: function(size) {
                Rectangle4.init.call(this, size, size);
        },
        id: "s4" + Math.random()
});

// also copy from rectangle
Triangle4 = make4(Rectangle4,{
        id: "t4" + Math.random(),
        getArea: function() {
                return 0.5 * this.length * this.width;
        }
});
ready

Revisions

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

  • Revision 1: published by Kyle Simpson on
  • Revision 2: published by Kyle Simpson on