9
// Case A
function Constructor() {
  this.foo = function() {
    ...
  };
  ...
}

// vs 
// Case B
function Constructor() {
  ...
};

Constructor.prototype.foo = function() {
  ...
}

One of the main reasons people advise the use of prototypes is that .foo is created once in the case of the prototype where as this.foo is created multiple times when using the other approach.

However one would expect interpreters can optimize this. So that there is only one copy of the function foo in case A.

Of course you would still have a unique scope context for each object because of closures but that has less overhead then a new function for each object.

Do modern JS interpreters optimise Case A so there is only one copy of the function foo ?

Raynos
  • 166,823
  • 56
  • 351
  • 396

3 Answers3

10

Yes, creating functions uses more memory.

... and, no, interpreters don't optimize Case A down to a single function.

The reason is the JS scope chain requires each instance of a function to capture the variables available to it at the time it's created. That said, modern interpreters are better about Case A than they used to be, but largely because the performance of closure functions was a known issue a couple years ago.

Mozilla says to avoid unnecessary closures for this reason, but closures are one of the most powerful and often used tools in a JS developer's toolkit.

Update: Just ran this test that creates 1M 'instances' of Constructor, using node.js (which is V8, the JS interpreter in Chrome). With caseA = true I get this memory usage:

{
    rss: 212291584,       //212 MB
    vsize: 3279040512,    //3279 MB
    heapTotal: 203424416, //203 MB
    heapUsed: 180715856   //180 MB
}

And with caseA = false I get this memory usage:

{
    rss: 73535488,       //73 MB
    vsize: 3149352960,   //3149 MB
    heapTotal: 74908960, //74 MB
    heapUsed: 56308008   //56 MB
}

So the closure functions are definitely consuming significantly more memory, by almost 3X. But in the absolute sense, we're only talking about a difference of ~140-150 bytes per instance. (However that will likely increase depending on the number of in-scope variables you have when the function is created).

Andrew Nessin
  • 1,206
  • 2
  • 15
  • 22
broofa
  • 37,461
  • 11
  • 73
  • 73
  • Can we have some references that define "better" and "modern interpreters" – Raynos Sep 16 '11 at 23:39
  • Your tests match with what I found in mine as well--- I also added some large code blocks inside of the function to test if that made the memory balloon any faster - and it didn't... Code inside the function takes no extra memory. – gnarf Sep 17 '11 at 00:38
  • Oh - and I also logged out memory usage before and after the test to measure the diff before objects were created – gnarf Sep 17 '11 at 00:44
  • Might be worth checking this against chrome canary v32 - looks like there have been some improvements. – UpTheCreek Oct 08 '13 at 20:59
2

I believe, after some brief testing in node, that in both Case A and B there is only one copy of the actual code for the function foo in memory.

Case A - there is a function object created for each execution of the Constructor() storing a reference to the functions code, and its current execution scope.

Case B - there is only one scope, one function object, shared via prototype.

gnarf
  • 105,192
  • 25
  • 127
  • 161
  • Can you post your test code? I'm sure there is some optimization going on inside the interpreter to avoid *parsing* the function code each time, but each pass through the constructor has to capture references to any in-scope variables so they can be properly resolved when the function is invoked. – broofa Sep 16 '11 at 23:52
  • @broofa - comparing my code to yours, its basically the same... :) - I just dumped like 40 lines of code from something else inside the function to test that... – gnarf Sep 17 '11 at 00:39
0

The javascript interpreters aren't optimizing prototype objects either. Its merely a case of there only being one of them per type (that multiple instances reference). Constructors, on the other hand, create new instances and the methods defined within them. So by definition, this really isn't an issue of interpreter 'optimization' but of simply understanding what's taking place.

On a side note, if the interpreter were to try and consolidate instance methods you would run into issues if you ever decided to change the value of one in a particular instance (I would prefer that headache not be added to the language) :)

Marlin
  • 751
  • 4
  • 8
  • the compiler does optimise the function, it doesn't optimise the scopecontext though. – Raynos Sep 16 '11 at 23:39
  • 2
    How each interpreter handles duplicates doesn't realy seem like the issue though, the interpreter will need to establish a difference between multiple instance methods and that difference will consume more memory – Marlin Sep 16 '11 at 23:44
  • 1
    For case A, I would expect a compiler to see that there are no local variables in the constructor so there is no need to keep its variable object on the scope chain of instances. If local variables are used, it can't do that unless it is smart enough to optimise the variable object away if the inner function doesn't reference them. Regardless, I would use the prototype approach as its neater and more easily maintained (IMO of course). – RobG Sep 17 '11 at 00:04