Переменные класса в Javascript - PullRequest
17 голосов
/ 04 ноября 2008

У меня возникли некоторые проблемы при попытке заставить переменные класса работать в javascript.

Я думал, что понял модель наследования прототипа, но, очевидно, нет. Я предполагал, что поскольку прототипы будут совместно использоваться объектами, то и их переменные будут такими же.

Вот почему этот кусочек кода смущает меня.

Как правильно реализовать переменные класса?

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: Таким образом, кажется, что все подтверждают тот факт, что, увеличивая переменную экземпляра, мы не влияем на прототип. Это хорошо, это то, что я задокументировал в своем примере, но разве это не похоже на ошибку в дизайне языка? Почему это поведение было бы желательным? Я нахожу странным, что когда переменная экземпляра не определена, мы переходим по скрытой ссылке на прототип, где получаем значение переменной, но копируем ее в объект экземпляра.

Я также понимаю, что это не java / c ++ / ruby ​​/ python, это другой язык. Мне просто любопытно, почему это поведение может быть хорошим.

Ответы [ 8 ]

15 голосов
/ 04 ноября 2008

Статические (на уровне класса) переменные могут быть сделаны следующим образом :

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

Ваш вывод кажется странным из-за того, как javascript обрабатывает прототипы . Вызов любого метода / получение переменной экземпляра объекта сначала проверяет экземпляр, затем прототип . т.е.

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) 
8 голосов
/ 04 ноября 2008
I assumed that since prototypes will be shared between objects then so will their variables.

Они есть, но это:

a.shared++

не делает то, что вы думаете, что делает. Фактически (приблизительно) синтаксис сахара для:

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

(-1 означает возврат значения перед инкрементом, не то, что вы на самом деле используете значение retrun, но все же.)

Так что это на самом деле делает присвоение a.shared. Когда вы присваиваете элементу экземпляра, вы всегда пишете собственным членам этого экземпляра, не касается любых членов любого из его прототипов. Это то же самое, что сказать:

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

Таким образом, ваш новый a.shared скрывает прототип.shared, не изменяя его. Другие экземпляры класса A будут по-прежнему отображать значение 1 прототипа. Если вы удалите a.shared, вы снова сможете увидеть скрытую за ним переменную прототипа.

5 голосов
/ 04 ноября 2008

Если вы хотите иметь переменную класса, что-то вроде статической переменной в Java, тогда вы можете объявить переменную в родительском классе, но тогда вам не следует обращаться к ней как к переменной дочерних объектов. Эта статья содержит хороший пример класса Circle с переменной Circle.PI = 3.14, в то время как все экземпляры Circle обращаются к нему как Circle.PI (вместо c.PI).

Поэтому мой ответ таков: если вы хотите иметь переменную класса shared в classA, вы должны объявить переменную общего доступа в classA, а позже вам следует использовать classA.shared вместо a.shared. Изменение a.shared никогда не приведет к изменению classA.shared.

3 голосов
/ 04 ноября 2008

Вы просто помещаете член в «класс», который в JavaScript является функцией, которая создает объекты:

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());
2 голосов
/ 04 ноября 2008

Увеличение свойства shared через экземпляр делает его свойством этого экземпляра, поэтому вы наблюдаете это поведение.

Как только вы это сделаете, вы никогда не получите доступ к прототипу этого свойства через экземпляр, но к его собственному свойству.

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

Вот почему правильным решением является использование ConstructorA.shared, как предлагалось во многих ответах на данный момент, и всегда доступ к нему через функцию конструктора, а не экземпляр.

Это может помочь понять, что в JavaScript нет такого понятия, как класс. «Экземпляры», созданные с помощью оператора new, являются просто объектами, которые были созданы определенной функцией конструктора и имеют определенную цепочку прототипов. Вот почему a.shared не сможет получить доступ к ConstructorA.shared - доступ к свойству подразумевает просмотр рассматриваемого объекта на предмет указанного свойства и сбой этого, обход цепочки прототипов в поисках свойства, но создание функции-создателя объект не является частью цепочки прототипов.

1 голос
/ 09 августа 2012

Это потому, что прототипы не являются определениями классов. Прототипические переменные не являются статическими переменными. Подумайте о слове прототип. Это не модель, используемая для построения объекта - это пример объекта, который нужно продублировать.

1 голос
/ 04 ноября 2008

Если вы создадите экземпляр этого класса (a = new classA), то изменение этого экземпляра a не изменит сам базовый класс. Экземпляры classA будут наследовать все от classA.prototype, но это не применяется в обратном направлении, изменение a не изменит classA.
Если у вас есть два экземпляра, например a1 = new classA и a2 = new classA, вы можете внести изменения как в a1, так и в a2, не влияя на другие. Изменение classA.prototype с другой стороны будет видно в обоих из них.
Переменная shared экземпляра a будет иметь значение по умолчанию, пока ей не будет присвоено новое значение. Значением по умолчанию является значение classA.prototype.shared.

0 голосов
/ 04 ноября 2008

То, что вы определяете, не является переменной класса, это значение по умолчанию для переменной экземпляра.

Переменные класса должны быть определены непосредственно в классе, что означает непосредственно в функции constrctor.

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

Когда вы объявляете переменную в прототипе, эта переменная будет наследоваться всеми экземплярами как переменные экземпляра (как методы) и будет переопределена, если вы измените ее в экземпляре (точно так же как методы).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...