Class Variables in Javascript

2019-02-06 04:11发布

问题:

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:

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.



回答2:

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) 


回答3:

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.



回答4:

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 + this.x;
}

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

ClassA.shared = "Hello ";
console.log(inst1.foo());
console.log(inst2.foo());
ClassA.shared = "Good bye ";
console.log(inst1.foo());
console.log(inst2.foo());


回答5:

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.



回答6:

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.



回答7:

It's because prototypes are not class definitions. Prototypical variables are not static variables. Think of the word prototype. It's not a model used to build an object -- it is an example object to be duplicated.



回答8:

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).