Добро пожаловать в цепочку прототипов!
Давайте посмотрим, как это выглядит на вашем примере.
Эта проблема
Когда вы вызываете new Thing()
, вы создаете новый объект со свойством relatedThings
, которое ссылается на массив. Итак, мы можем сказать, что у нас есть это:
+--------------+
|Thing instance|
| |
| relatedThings|----> Array
+--------------+
Затем вы назначаете этот экземпляр ThingA.prototype
:
+--------------+
| ThingA | +--------------+
| | |Thing instance|
| prototype |----> | |
+--------------+ | relatedThings|----> Array
+--------------+
Таким образом, каждый экземпляр ThingA
будет наследоваться от экземпляра Thing
. Теперь вы создадите ThingA1
и ThingA2
и назначите новый экземпляр ThingA
каждому из их прототипов, а позже создадите экземпляры ThingA1
и ThingA2
(и ThingA
и Thing
, но не показаны здесь).
Отношение теперь такое (__proto__
— это внутреннее свойство, связывающее объект с его прототипом):
+-------------+
| ThingA |
| |
+-------------+ | prototype |----+
| ThingA1 | +-------------+ |
| | |
| prototype |---> +--------------+ |
+-------------+ | ThingA | |
| instance (1) | |
| | |
+-------------+ | __proto__ |--------------+
| ThingA1 | +--------------+ |
| instance | ^ |
| | | v
| __proto__ |-----------+ +--------------+
+-------------+ |Thing instance|
| |
| relatedThings|---> Array
+-------------+ +--------------+ +--------------+
| ThingA2 | | ThingA | ^
| | | instance (2) | |
| prototype |---> | | |
+-------------+ | __proto__ |--------------+
+--------------+
+-------------+ ^
| ThingA2 | |
| instance | |
| | |
| __proto__ |-----------+
+-------------+
И поэтому каждый экземпляр ThingA
, ThingA1
или ThingA2
относится к одному и тому же экземпляру массива.
Это не то, что вам нужно!
Решение
Чтобы решить эту проблему, каждый экземпляр любого "подкласса" должен иметь собственное свойство relatedThings
. Вы можете добиться этого, вызвав родительский конструктор в каждом дочернем конструкторе, аналогично вызову super()
в других языках:
function ThingA() {
Thing.call(this);
}
function ThingA1() {
ThingA.call(this);
}
// ...
Это вызывает Thing
и ThingA
и устанавливает this
внутри этих функций в первый аргумент, который вы передаете .call
. Подробнее о .call
[MDN]< /em> и this
[MDN] суп>эм>а>.
Это само по себе изменит приведенную выше картинку на:
+-------------+
| ThingA |
| |
+-------------+ | prototype |----+
| ThingA1 | +-------------+ |
| | |
| prototype |---> +--------------+ |
+-------------+ | ThingA | |
| instance (1) | |
| | |
| relatedThings|---> Array |
+-------------+ | __proto__ |--------------+
| ThingA1 | +--------------+ |
| instance | ^ |
| | | |
|relatedThings|---> Array | v
| __proto__ |-----------+ +--------------+
+-------------+ |Thing instance|
| |
| relatedThings|---> Array
+-------------+ +--------------+ +--------------+
| ThingA2 | | ThingA | ^
| | | instance (2) | |
| prototype |---> | | |
+-------------+ | relatedThings|---> Array |
| __proto__ |--------------+
+--------------+
+-------------+ ^
| ThingA2 | |
| instance | |
| | |
|relatedThings|---> Array |
| __proto__ |-----------+
+-------------+
Как видите, у каждого экземпляра есть собственное свойство relatedThings
, которое ссылается на другой экземпляр массива. В цепочке прототипов все еще есть relatedThings
свойство, но все они затенены свойством экземпляра.
Лучшее наследование
Кроме того, не устанавливайте прототип с помощью:
ThingA.prototype = new Thing();
На самом деле вы не хотите создавать здесь новый экземпляр Thing
. Что произойдет, если Thing
ожидаются аргументы? Какой бы вы прошли? Что, если вызов конструктора Thing
имеет побочные эффекты?
Что вам на самом деле нужно, так это подключить Thing.prototype
к цепочке прототипов. Вы можете сделать это с помощью Object.create
[MDN]эм>а>:
ThingA.prototype = Object.create(Thing.prototype);
Все, что происходит при выполнении конструктора (Thing
), произойдет позже, когда мы действительно создадим новый экземпляр ThingA
(путем вызова Thing.call(this)
, как показано выше).
person
Felix Kling
schedule
23.02.2013