views:

4778

answers:

8

I'm having a bit of trouble trying to get class variables to work in javascript.

I thought that I understood the prototype inheritance model, but obviously not. I assumed that since prototypes will be shared between objects then so will their variables.

This is why this bit of code confuses me.

What is the correct way to implement class variables?

function classA() {};

classA.prototype.shared = 0;

a = new classA;

//print both values to make sure that they are the same
classA.prototype.shared;
a.shared;

//increment class variable
classA.prototype.shared++;

//Verify that they are each 1 (Works)
classA.prototype.shared;
a.shared;

//now increment the other reference
a.shared++;

//Verify that they are each 2 (Doesn't Work)
classA.prototype.shared;
a.shared;

UPDATE: So it seems that everyone is confirming the fact that by incrementing the instance's variable we don't affect the prototype. This is fine, this is what I have documented in my example, but doesn't this seem like an error in the design of the language? Why would this behavior be desirable? I find it weird that when the instance's var is undefined we follow the hidden link to the prototype where we get the value of the var, but we copy it into the instance object.

I also understand that this isn't java/c++/ruby/python, it's a different language. I'm just curious as to why this behavior might be good.

+1  A: 

If you instantiate that class (a = new classA), then modifying that instance a won't change the base class itself. Instances of classA will inherit everything from classA.prototype, but that doesn't apply backwards, changing a won't change classA.
If you have two instances like a1 = new classA and a2 = new classA then you can make changes to both a1 and a2 without effecting the other. Changing classA.prototype on the other hand will be visible in both of them.
The variable shared of instance a will have the default value until it is given a new value. The default value is the value of classA.prototype.shared.

Chei
This really doesn't make sense to me. In the beginning it seems that "a" searches for the variable shared, doesn't have it and find it in the prototype. But later it actually contains a copy of the variable. I mean after this code above is run, by incrementing classA.shared, "a" isn't affected
esiegel
Also, It's not just that "instance of classA will inherit everything from classA.prototype." It is actually linked in the beginning as can be seen when I increment the prototypes.shared and this also incremented the a.shared.
esiegel
I just added a third paragraph describing that a.shared will have a default value of classA.prototype.shared until it is given a new value.
Chei
a.shared does not have a default value. A.shared is undefined within the instance A, and is actually found in the prototype. It's just that after you write a.shared++ this strangely, and in my mind incorrectly, copy the value from the prototype to the instance. Now forevermore shared exists in A
esiegel
A: 

You just put the member right on the "class" which in JavaScript is the function that constructs objects:

function ClassA(x) { this.x = x; }
ClassA.shared = "";
ClassA.prototype.foo = function() {
    return ClassA.shared + x;
}

var inst1 = new ClassA("world");
var inst2 = new ClassA("mars");

ClassA.shared = "Hello ";
document.write(inst1.foo());
document.write(inst2.foo());
ClassA.shared = "Good bye ";
document.write(inst1.foo());
document.write(inst2.foo());
Frank Krueger
This doesn't work. Try instantiating an instance a = new classA. This variable will not have access to a member shared.
esiegel
No, because it isn't a class member, its a property of the ClassA object itself.
roryf
Why is this being voted down? It's one of the answers which gives the correct solution.
insin
I don't understand the voting down either. Class variables are associated with constructors. Just because you have to use `ClassName.prop` syntax doesn't mean it's wrong. C++ and C# require that you use the same syntax.
Frank Krueger
I apologize for the voting down, but I did it originally because your answer didn't address my question. It didn't show instances sharing a variable. It was just code, and I interpreted as wrong even though this is the only way to "share" class variables in javascript. Now I can't undo the vote.
esiegel
+4  A: 

If you want to have a class variable, something like a static variable in Java, then you can declare a variable in the parent class, but then you shouldn't access it as a variable of the child objects. This article has a nice example of the class Circle having the variable Circle.PI = 3.14 while all the instances of Circle access it as Circle.PI (instead of c.PI).

So my answer is that if you want to have a class variable shared in classA then you shall declare the variable shared in classA, and later you should use classA.shared instead of a.shared. Changing a.shared will never result in classA.shared being changed.

Chei
+4  A: 

Static (class level) variables can be done like this:

function classA(){
    //initialize
}

classA.prototype.method1 = function(){
    //accessible from anywhere
    classA.static_var = 1;
    //accessible only from THIS object
    this.instance_var = 2;
}

classA.static_var = 1;  //This is the same variable that is accessed in method1()

Your output seems strange because of the way javascript handles prototypes. Calling any method / retreiving a variable of an instantiated object checks the instance first, THEN the prototype. i.e.

var a = new classA();
classA.prototype.stat = 1;

// checks a.stat which is undefined, then checks classA.prototype.stat which has a value
alert(a.stat); // (a.stat = undefined, a.prototype.stat = 1)

// after this a.stat will not check the prototype because it is defined in the object.
a.stat = 5;  // (a.stat = 5, a.prototype.stat = 1)

// this is essentially a.stat = a.stat + 1;
a.stat++; // (a.stat = 6, a.prototype.stat = 1)
Daniel Beardsley
+1  A: 

Incrementing the shared property via the instance makes it a property of that instance, which is why you're seeing this behaviour.

Once you've done that, you'll never be accessing the prototype for that property through the instance, but its own property.

>>> function ConstructorA() {};
>>> ConstructorA.prototype.shared = 0;
>>> var a = new ConstructorA();
>>> ConstructorA.prototype.shared++;
>>> a.shared
1
>>> a.hasOwnProperty("shared")
false
>>> a.shared++;
>>> a.hasOwnProperty("shared")
true

This is why the correct solution is to use ConstructorA.shared, as suggested in many of the answers so far, and always access it through the constructor function, not an instance.

It might help to consider that there's no such thing as a class in JavaScript. "Instances" created with the new operator are just objects which have been created by a particular constructor function and have a particular prototype chain. This is why a.shared won't be able to access ConstructorA.shared - property access involves looking at the object in question for the named property, and failing that, walking its prototype chain looking for the property, but the constructor function which created the object isn't part of the prototype chain.

insin
A: 

What you are defining is not a class variable, it is a default value for an instance variable.

Class variables should be defined directly in the class, which means directly in the constrctor function.

function ClassA()
{
    ClassA.countInstances = (ClassA.countInstances || 0) + 1;
}
var a1 = new ClassA();
alert(ClassA.countInstances);
var a2 = new ClassA();
alert(ClassA.countInstances);

When you are declaring a variable in the prototype, this variable will be inherited by all instances as instance variables (just like methods) and will ve overriden if you change it in the instance (just like methods).

Vincent Robert
A: 

I suggest you this read: http://www.3site.eu/doc/

kentaromiura
+1  A: 
I assumed that since prototypes will be shared between objects then so will their variables.

They are, but this:

a.shared++

is not doing what you think it's doing. It's in fact (approximately) sugar syntax for:

(a.shared= a.shared+1)-1

(the -1 being to return the pre-increment value, not that you're actually using the retrun value, but still.)

So this is actually doing an assigment to a.shared. When you assign to an instance member you are always writing to that instance's own members, not touching any members of any of its prototypes. It's the same as saying:

classA.prototype.shared= 1;
a.shared= 2;

So your new a.shared hides the prototype.shared without altering it. Other instances of classA would continue to show the prototype's value 1. If you deleted a.shared you would once again be able to see the prototype's variable that was hidden behind it.

bobince
Why would javascript allow this? Don't you view this as an error in the design of the language? I mean, when we increment an instance, whose variable exists in the prototype, we first have to get the vars value in the prototype, but strangely we then assign the value to the instance? WEIRD.
esiegel
It is a bit weird, yeah, but far from the weirdest thing in JavaScript! It is a language full of traps.
bobince