views:

98

answers:

1

Given this very familiar model of prototypal construction:

function Rectangle(w,h) {
    this.width = w;
    this.height = h;
}
Rectangle.prototype.area = function() { 
    return this.width * this.height;
};

Can anyone explain why calling new Rectangle(2,3) is consistently 10x FASTER than calling Rectangle(2,3) without the 'new' keyword? I would have assumed that because new adds more complexity to the execution of a function by getting prototypes involved, it would be slower.

Example:

var myTime;
function startTrack() {
    myTime = new Date();
}
function stopTrack(str) {
    var diff = new Date().getTime() - myTime.getTime();
    println(str + ' time in ms: ' + diff);
}

function trackFunction(desc, func, times) {
    var i;
    if (!times) times = 1;
    startTrack();
    for (i=0; i<times; i++) {
        func();
    }
    stopTrack('(' + times + ' times) ' + desc);
}

var TIMES = 1000000;

trackFunction('new rect classic', function() {
    new Rectangle(2,3);
}, TIMES);

trackFunction('rect classic (without new)', function() {
    Rectangle(2,3);
}, TIMES);

Yields (in Chrome):

(1000000 times) new rect classic time in ms: 33
(1000000 times) rect classic (without new) time in ms: 368

(1000000 times) new rect classic time in ms: 35
(1000000 times) rect classic (without new) time in ms: 374

(1000000 times) new rect classic time in ms: 31
(1000000 times) rect classic (without new) time in ms: 368
+4  A: 

When you call the function without "new", what is it that you suspect "this" is pointing to? It'll be "window." Updating that is slower than updating the freshly-built new object you'll be using when you invoke it with "new".

Change the second version to this:

trackFunction('rect classic (without new)', function() {
    Rectangle.call({}, 2,3);
}, TIMES);

and see what you get. Another thing to try would be this:

trackFunction('rect with constant object', (function() {
  var object = { height: 0, width: 0 };
  return function() {
    Rectangle.call(object, 2, 3);
  };
})());

That will save on the cost of rebuilding the dummy object on each iteration.

Pointy
You are absolutely right. Thanks!
Matthew Taylor
When I do that above, I get around 148 ms execution on the new object. Not as fast as calling new, but much faster than touching the document.
Matthew Taylor
Right - also note that in the second version there's the overhead of calling the "call" function too. I suspect that V8 has a pretty well-optimized implementation of "new" (in fact that's pretty clear from your numbers)!
Pointy
Also note that Rectangle(2,3) does not actually create anything, it simply (tries) to update the window height and width
plodder
And neither does Rectangle.call({}, 2,3) for that matter. So not sure how valid these comparisons are
plodder
It doesn't create anything, but it has something other than "window" for the "this" pointer to reference. Actually I'll update the answer with another idea. Oh, and note, @Angus, that it **does** create something - the empty object that's the first argument of "call".
Pointy
@Pointy: but the empty object is never returned (unlike the implicit `return this` present in all `new` invocations).
Roatin Marth
True, but that's not really what the original question was worrying about. Since (presumably) it's *more* work to return something, and the topic under investigation is why the "new" version is already *faster*, I don't see why making the non-"new" invocation return something would be of interest. The OP is certainly free to add a return statement, of course.
Pointy