Наследование Javascript: переменная массива родителя сохраняет значение - PullRequest
8 голосов
/ 16 февраля 2012

Я пытаюсь использовать наследование в JavaScript здесь, и обнаружил проблему со значением массива в Родительском классе, который наследуется Дочерним классом.Ниже приведен код нормального наследования:

var Parent = function() {
    this.list = [];
};

var Child = function() {};

Child.prototype = new Parent;
Child.prototype.constructor = Child;

var obj1 = new Child;

obj1.list.push("hello");

console.log(obj1.list); // prints ["hello"];

Когда я инициализирую новый Child объект (наследует Parent, который содержит переменную массива с именем list ) в obj1 и попытался выдвинуть obj1.list со значением "привет", obj1.list печатает ["привет"] .. пока все хорошо.

проблема возникает, когда я сделал приведенный выше пример и попытался инициализировать новый дочерний объект obj2 , а затем нажать obj2 ' список с помощьюзначение "до свидания" и obj2.list теперь печатает ["привет", "до свидания"].(См. Код ниже:)

var obj2 = new Child;

obj2.list.push("goodbye");

console.log(obj2.list); // prints ["hello", "goodbye"];

Я мог бы ошибиться здесь, но массив list в Parent каким-то образом сохраняет значение, и я нене знаю, почему.

Это большая проблема, потому что если я повторно использую класс Parent для многих других дочерних классов, как в предыдущем случае, если Parent имеет переменную массива, совместно используемуюдля его дочерних классов значение будет также доступно и другим дочерним классам, что для меня неожиданно.

То, что я ожидал, класс Child представляет новый объект, то же самое происходит Родительский класс, когда Дочерний класс инициализируется в obj1 , который затем, когда я инициализирую новый Дочерний объект в obj2 , выдаваемое значение из obj1 не должно использоваться совместно с obj2 .

- Вопрос -

Может кто-нибудь помочь мне выяснить, почему список (переменная массива Parent ) в приведенном выше примере сохраняет значения / долязначения, которые инициируются дочерними объектами (в вышеупомянутом случае obj1 и obj2 )?

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

Ответы [ 2 ]

12 голосов
/ 16 февраля 2012

Когда у дочернего объекта есть свойство, унаследованное от объекта-прототипа, в действительности происходит то, что у дочернего объекта есть ссылка на прототип, который содержит это свойство. У ребенка нет его собственной копии. Таким образом, оба ребенка используют один и тот же массив & mdash; один на (один) Parent объекте-прототипе, который вы присвоили Child.prototype.

Сначала несколько картинок, затем больше текста. : -)

new Parent() дает вам это:

+-----------------+
| Parent instance |
+-----------------+
| list = []       |
+-----------------+

... которую вы затем присваиваете Child.prototype.

Тогда new Child() дает вам это:

+------------------+
| Child instance 1 |
+------------------+        +-----------------+
| (prototype)      |------->| Parent instance |
+------------------+        +-----------------+
                            | list = []       |
                            +-----------------+

Выполнение new Child() снова дает вам еще один:

+------------------+      +------------------+
| Child instance 1 |      | Child instance 2 |
+------------------+      +------------------+
| (prototype)      |---+  | (prototype)      |---+
+------------------+   |  +------------------+   |
                       |                         |
                       |                         |     +-----------------+
                       +-------------------------+---->| Parent instance |
                                                       +-----------------+
                                                       | list = []       |
                                                       +-----------------+

Итак, как вы можете видеть, все Child экземпляры совместно используют один и тот же Parent экземпляр (их прототип), на котором есть массив.

Когда вы говорите:

var obj = new Child();
obj.list.push("foo");

... вот что делает движок JavaScript, когда видит obj.list:

  1. Проверяет, имеет ли obj свое собственное свойство, называемое list. Если нет, то:
  2. Проверяет, имеет ли прототип obj свое собственное свойство, называемое list. Если нет, то:
  3. Проверяет, имеет ли прототип прототипа obj свое собственное свойство, называемое list.
  4. Etc. пока у нас не закончатся прототипы.

В вашем случае, поскольку obj не имеет своего собственного list свойства, движок ищет свой прототип (экземпляр Parent, который вы присвоили Child.prototype), который в этот случай имеет свойство. Так что этот используется. Он не копируется ни ребенку, ни чему-либо другому, он используется . И, конечно же, поскольку это массив, добавление чего-либо в массив фактически выталкивает его в массив.

Если бы вы присваивали что-то obj.list, тогда obj получило бы свое собственное list свойство, разрывая цепочку. Таким образом, включение this.list = []; в Child даст каждому ребенку свой список.

Вы увидите это всякий раз, когда на прототипе есть ссылка на объект , где объект - это тип, который можно изменить («изменяемый» объект). Массивы, даты, обычные объекты ({}), RegExps и т. Д., Все они имеют состояние, и все они могут быть изменены, так что вы увидите это с ними. (String экземпляры являются неизменяемыми, поэтому, несмотря на то, что происходит то же самое, вы не видите никакого эффекта, потому что строка не может быть изменена.)

С примитивами, хотя вы наследуете их, вы не можете изменить их состояние (вы можете только заменить их новым примитивом с другим состоянием), так что вы не увидите этот же эффект. Таким образом, если obj наследует свойство foo от его прототипа, а foo равно 42, alert(obj.foo) получит значение из прототипа и покажет "42". Единственный способ изменить foo - это сказать obj.foo = 67 или аналогичный & mdash; который дает obj его собственную копию foo, отличную от копии прототипа. (Это верно, даже если вы используете такие вещи, как ++ и --, например, ++obj.foo; это действительно оценивается как obj.foo = obj.foo + 1).

3 голосов
/ 16 февраля 2012

То, что вам здесь не хватает, это то, что Parent.list создается только один раз;когда вы копируете его прототип в Child.prototype.

Создание "подкласса" в JavaScript не автоматически вызывает родительский конструктор.Таким образом, все экземпляры Child будут совместно использовать один и тот же экземпляр массива.

Изменение конструктора Child для вызова родительского конструктора должно быть лекарством, которое вы ищете для вашей болезни:

var Child = function() {
    Parent.call(this);
};

Очевидно, у вас уже есть некоторые знания по этому вопросу, но если вам интересно, вот несколько текстов по этой теме, которые стоит прочитать:

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