1

Related Questions, but no mention of my specific 'bug' -

prototypes vs extending objects

Advantages of using prototype, vs defining methods straight in the constructor?

I have a performance issue I don't understand. I was creating a class with the ability to be both callable and have methods (similar to jQuery).

I allowed an 'opt out' of the callable part, assuming that it would be slower to extend a function with methods than to use a prototype. This assumption is correct in the most basic case.

However, when I actually timed my code and looked at the memory usage, I was surprised to discover the prototype approach being slower AND taking up more memory.

After looking into it, it became clear that it was only less efficient when calling the 'init' method that binds a DOM element to 'this'. If you comment out the call to init (line 49 in the fiddle), the prototype method is much faster, as I would expect.

jsfiddle here - http://jsfiddle.net/pnsfogem/1/

Edit: jsPerf laying out all of Bergi's suggestions below - http://jsperf.com/different-types-of-constructors-vs-extend

After running these perfs, it does look like it's only in the version of Chrome I'm running..

and all of the code needed to replicate.

var methods = {
    select: function(selector){
        var $this = this;
        var scopedMethods = {
            newMethod1: function( newParam ){
                $this.newMethod1( selector, newParam );
            },
            newMethod2: function( newParam ){
                $this.newMethod2( selector, newParam );
            }
        };
        return scopedMethods;
    },
    init: function(){
        // console.log(this); // Looks correct for all 2000 elements
        this.$el = $( "<div>" ).appendTo( $("body") );
    },
    newMethod1: function( selector, newParam ){
        // do stuff
    },
    newMethod2: function( selector, newParam ){
        // do stuff
    }
};

function getConstructor( noQuery ){

    var returnedInstance;

    if ( noQuery ){
        var constructor = function(){};
        constructor.prototype = methods;
        returnedInstance = new constructor();
        // Usage:
        // var s = getConstructor( 'justPrototype' );
        // s.newMethod1( '#whatever', 'stuff' );
    }
    else {
        returnedInstance = function(){
            return returnedInstance.select.apply( returnedInstance, arguments );
        };
        $.extend( returnedInstance, methods );
        // Can use either of these ways:
        // var s = getConstructor();
        // s.newMethod1( '#whatever', 'stuff' );
        // s( '#whatever' ).newMethod1( 'stuff' );
    }

    returnedInstance.init();

    return returnedInstance;

}

// When calling init
// This is both faster and more memory efficient. Why?
var arr1 = [];
console.time('normal');
for (var i=0; i<1000;i++){
    arr1.push( getConstructor() );
}
console.timeEnd('normal');
// arr1[0].$el != arr1[1].$el

var arr2 = [];
console.time('prototype');
for (var i=0; i<1000;i++){
    arr2.push( getConstructor( 'justPrototype' ) );
}
console.timeEnd('prototype');
// arr2[0].$el != arr2[1].$el

so, my question is

Why is this the case? Am I doing something wrong?

Once they're instantiated, I would expect them to handle adding new properties to be relatively the same performance wise, but it seems to slow down the prototype approach by 10x.

( Obviously, there's other benefits / trade-offs to allowing access to a prototype vs having a function with no prototype, but I think they're outside the scope of this question )

Community
  • 1
  • 1
iabw
  • 1,098
  • 1
  • 9
  • 23
  • In which browser did you test? How did you measure memory? What were your results? By testing in Opera 12, I get very similar results on both methods, every somewhere around 300ms; it doesn't vary much when I change anything. – Bergi Sep 04 '14 at 12:20
  • I'm using Chrome 37. I would argue that they shouldn't be the same though. Without the call to .init, the prototype approach is 10x as fast. In my actual class, where there are many more bound methods, the prototype approach takes twice as long as using $.extend – iabw Sep 04 '14 at 12:22
  • I measured memory using Chrome's profiler. I can add specific results from that if it might be relevant to the answer. Again, it was about 2x as large for prototype in my actual use case. I didn't check memory on this minimal case. – iabw Sep 04 '14 at 12:25
  • I also tried moving `var constructor = function(){};constructor.prototype = methods;` out of getConstructor, but didn't notice a significant difference. – iabw Sep 04 '14 at 12:29
  • It appears to be the order they're called in. The 'new Function' approach is only faster in Chrome when it's called before the prototype loop. If I reverse them, the opposite is true, with about the same percentages. Seems like some kind of bug. – iabw Sep 04 '14 at 14:28
  • I guess that's because of jQuery. The weight of the object construction is negligible in comparison to what the jQuery in `init` does, and it might cause heavy (garbage collection, DOM update) slowdowns in subsequent tests. – Bergi Sep 04 '14 at 14:40

1 Answers1

2

I was creating a class with the ability to be both callable and have methods (similar to jQuery).

Not really. jQuery collection instances are not callable.

Am I doing something wrong?

Your "prototype" branch looks a bit odd. On every invocation, you're creating a new constructor function. Making that global should help a little bit. Also, the empty constructor with extra init method is a very unusual pattern. You might want to call the init method from the constructor (see below), or even directly use

var constructor = methods.init;
constructor.prototype = methods;
function get() {
    …
    return new constructor();
}

If you used the pattern simply to create an object with methods as a prototype, you rather should use Object.create:

…  returnedInstance = Object.create(methods);

Once they're instantiated, I would expect them to handle adding new properties to be relatively the same performance wise, but it seems to slow down the prototype approach by 10x.

No, properties are optimized a lot. Using unusual patterns can cause dramatic slowdowns. In V8 (Google Chrome's JS engine) for example, properties that are created during a constructor call are optimized, and no further slots are left open. When you craete a new property after the object was constructed, it might need to change its internal structure to a less optimized (but more add-property-friendly), which is rather slow. If this guess if correct, you should be able to see a significant speed-up by using

function constructor() {
    this.init();
}
constructor.prototype = methods;
…
return new constructor();
Bergi
  • 513,640
  • 108
  • 821
  • 1,164
  • I just meant that I was mimicking jQuery's API of $(), $().method and $.method(). The idea that I might just be un-optimizing something in the engine is a good one, but adding a property to an object after making it isn't exactly deep magic... – iabw Sep 04 '14 at 12:42
  • No, you're not mimicking jQuery's api. `$().method()` and `$.method()` are different things; what you are currently doing would actually allow `getConstructor(true).method()` or `getConstructor()(…).method()`. Is that really what you want? Placing static functions on a class (like jQuery does) is a very trivial thing actually. – Bergi Sep 04 '14 at 12:48
  • Adding a property to an object after making it is trivial to do in JavaScript. Writing an interpreter/compiler for JavaScript that handles dynamic properties [very] efficiently is deep magic. See https://developers.google.com/v8/design for an overview. – Bergi Sep 04 '14 at 12:50
  • None of your suggestions significantly improved my results. I just wouldn't expect a de-optimization to even happen on `this.prop = $('stuff')` or to end up slower than `$.extend`. Is that not weird? I suppose the engine could still be optimizing the extended Function? I'm setting up a jsperf with all your suggestions now. – iabw Sep 04 '14 at 13:11
  • 1
    I have now removed the DOM manipulation to see the "raw" results: http://jsperf.com/different-types-of-constructors-vs-extend/2 – Bergi Sep 04 '14 at 14:38
  • 1
    That does make the pure instantiation behave how I would expect, at least. – iabw Sep 04 '14 at 14:59
  • Yeah, and it shows the cost of creating a function object :-) – Bergi Sep 04 '14 at 15:02