views:

238

answers:

4

Normally, I've seen prototype functions declared outside the class definition, like this:

function Container(param) {
    this.member = param;
}
Container.prototype.stamp = function (string) {
    return this.member + string;
}

var container1 = new Container('A');
alert(container1.member);
alert(container1.stamp('X'));

This code produces two alerts with the values "A" and "AX".

I'd like to define the prototype function INSIDE of the class definition. Is there anything wrong with doing something like this?

function Container(param) {
    this.member = param;
    if (!Container.prototype.stamp) {
        Container.prototype.stamp = function() {
            return this.member + string;
        }
    }
}

I was trying this so that I could access a private variable in the class. But I've discovered that if my prototype function references a private var, the value of the private var is always the value that was used when the prototype function was INITIALLY created, not the value in the object instance:

Container = function(param) {
    this.member = param;
    var privateVar = param;
    if (!Container.prototype.stamp) {
        Container.prototype.stamp = function(string) {
            return privateVar + this.member + string;
        }
    }
}
var container1 = new Container('A');
var container2 = new Container('B');
alert(container1.stamp('X'));
alert(container2.stamp('X'));

This code produces two alerts with the values "AAX" and "ABX". I was hoping the output would be "AAX" and "BBX". I'm curious why this doesn't work, and if there is some other pattern that I could use instead.

EDIT: Note that I fully understand that for this simple example it would be best to just use a closure like this.stamp = function() {} and not use prototype at all. That's how I would do it too. But I was experimenting with using prototype to learn more about it and would like to know a few things:

  • When does it make sense to use prototype functions instead of closures? I've only needed to use them to extend existing objects, like Date. I've read that closures are faster.
  • If I need to use a prototype function for some reason, is it "OK" to define it INSIDE the class, like in my example, or should it be defined outside?
  • I'd like to understand why the privateVar value of each instance is not accessible to the prototype function, only the first instance's value.
A: 

You need to put the function on each specific instance instead of the prototype, like this:

Container = function(param) {
    this.member = param;
    var privateVar = param;

    this.stamp = function(string) {
        return privateVar + this.member + string;
    }
}
SLaks
@Slaks: I've revised the question to be more clear on what exactly I'm asking.
Tauren
A: 

To get the behavior you want you need to assign each individual object separate stamp() functions with unique closures:

Container = function(param) {
    this.member = param;
    var privateVar = param;
    this.stamp = function(string) {
        return privateVar + this.member + string;
    }
}

When you create a single function on the prototype each object uses the same function, with the function closing over the very first Container's privateVar.

By assigning this.stamp = ... each time the constructor is called each object will get its own stamp() function. This is necessary since each stamp() needs to close over a different privateVar variable.

John Kugelman
@John: Thanks for your answer. My question wasn't clear enough I guess, so I revised it. Please take a look at the edit.
Tauren
A: 

That is because privateVar is not a private member of the object, but part of stamp's closure. You could get the effect by always creating the function:

Container = function(param) {
    this.member = param;
    var privateVar = param;
    this.stamp = function(string) {
      return privateVar + this.member + string;
    }
}

The value of privateVar is set when the function is built, so you need to create it each time.

EDIT: modified not to set the prototype.

Kathy Van Stone
@Kathy: Thanks, I agree a closure is the way to go. Please see my edited question, as I added more details about what exactly I'm trying to ask.
Tauren
@Tauren The reason why the variable was only set to the first privateVar is that the function was only created once, so the closure was only created once.
Kathy Van Stone
@Kathy, thanks for the clarification, I see what's happening now.
Tauren
Note: In this case since the `stamp` function is being re-declared every time the constructor runs, the `privateVar`, accessible within the `stamp` method, will refer always to the value in the scope of the **last** instantiated object for any instance.
CMS
@CMS - you're right -- I'll modify the code to set stamp directly on this.
Kathy Van Stone
+3  A: 

When does it make sense to use prototype functions instead of closures?

Well, it's the most lightweight way to go, let's say you have a method in the prototype of certain constructor, and you create 1000 object instances, all those objects will have your method in their prototype chain, and all of them will refer to only one function object.

If you initialize that method inside the constructor, e.g. (this.method = function () {};), all of your 1000 object instances will have a function object as own property.

If I need to use a prototype function for some reason, is it "OK" to define it INSIDE the class, like in my example, or should it be defined outside?

Defining the members of a constructor's prototype inside itself, doesn't makes much sense, I'll explain you more about it and why your code doesn't works.

I'd like to understand why the privateVar value of each instance is not accessible to the prototype function, only the first instance's value.

Let's see your code:

var Container = function(param) {
    this.member = param;
    var privateVar = param;
    if (!Container.prototype.stamp) {  // <-- executed on the first call only
        Container.prototype.stamp = function(string) {
            return privateVar + this.member + string;
        }
    }
}

The key point about your code behavior is that the Container.prototype.stamp function is created on the first method invocation.

At the moment you create a function object, it stores the current enclosing scope in an internal property called [[Scope]].

This scope is later augmented when you call the function, by the identifiers (variables) declared within it using var or a FunctionDeclaration.

A list of [[Scope]] properties forms the scope chain, and when you access an identifier (like your privateVar variable), those objects are examined.

And since your function was created on the first method invocation (new Container('A')), privateVar is bound to the Scope of this first function call, and it will remain bound to it no matter how do you call the method.

Give a look to this answer, the first part is about the with statement, but in the second part I talk about how the scope chain works for functions.

CMS
@CMS: Great explanation, thanks so much! So if my prototype function only accesses `this` and not private vars, then it would work properly, even though it is defined INSIDE the class, right? Is there any drawback to doing it that way instead of declaring the prototype function after the class like I typically see?
Tauren
@Tauren, yes, there is a drawback, you will have a memory leak, for example in your code, all the variables declared in the first invocation within your constructor will be not garbage collected, because as you know now, the enclosing scope from where the `Container.prototype.stamp` function is created remains accessible even after the constructor ends its execution (a closure created). For that reason, some libraries like [Google's Closure](http://code.google.com/closure/library/) avoid closures for *private* members, they stick simply with naming conventions, e.g. `obj.__privateMember`.
CMS
@CMS: makes perfect sense, thanks!
Tauren