наследование прототипа javascript - PullRequest
16 голосов
/ 08 октября 2009

Это кажется противоречивым, но, вероятно, связано с тем, что я новичок в функции наследования прототипов javascript.

По сути, у меня есть два свойства базового класса, "список" и "имя". Я создаю экземпляры двух подклассов и присваиваю значения свойствам. Когда я создаю экземпляр второго подкласса, он получает значения списка из первого экземпляра подкласса, но только для «списка», а не для «имени». В чем дело?? Конечно, я бы предпочел, чтобы любые последующие экземпляры подкласса не получали значения из других экземпляров, но если это произойдет, это должно быть согласованно!

Вот фрагмент кода:

    function A()
    {
        this.list = [];
        this.name = "A";
    }
    function B()
    {
    }
    B.prototype = new A();

    var obj1 = new B();
    obj1.list.push("From obj1");
    obj1.name = "obj1";

    var obj2 = new B();

    //output:
    //Obj2 list has 1 items and name is A
    //so, the name property of A did not get replicated, but the list did
    alert("Obj2 list has " + obj2.list.length + " items and name is "+ obj2.name);    

Ответы [ 6 ]

13 голосов
/ 08 октября 2009

Дело в том, что с прототипами вы можете читать их целый день, и это не изменит основную структуру того, что указывает на что. Однако при первом выполнении назначения вы заменяете этот экземпляр тем, на что указывает это свойство.

В вашем случае вы фактически не переназначали свойство прототипа, вы изменили значение базовой структуры, найденной в этом свойстве.

Это означает, что все объекты, которые совместно используют прототип A, фактически разделяют реализацию A. Это означает, что любое состояние, переносимое в A, будет найдено во всех экземплярах B. В тот момент, когда вы выполните присваивая это свойство экземпляру B, вы фактически заменили то, на что указывает этот экземпляр (и только этот экземпляр) (я полагаю, это связано с тем, что в B есть новое свойство "name", которое попадает в область действия цепочка, прежде чем она достигнет реализации "name" в A).

EDIT:

Чтобы дать более явный пример того, что происходит:

B.prototype = new A();
var obj1 = new B();

Теперь, когда мы впервые читаем, интерпретатор делает что-то вроде этого:

obj1.name;

Интерпретатор: «Мне нужно имя свойства. Во-первых, проверьте, что Б. не имеет« имени », поэтому давайте продолжим цепочку областей действия. Далее, А. Ага! А имеет« имя », вернем это»

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

obj1.name = "Fred";

Интерпретатор: «Мне нужно присвоить свойству« имя ». Я нахожусь в области действия этого экземпляра из B, поэтому присваиваем 'Fred' B. Но я оставляю все остальное дальше вниз по одна цепочка области действия (т. е. A) "

Теперь, в следующий раз, когда вы прочитаете ...

obj1.name;

Интерпретатор: «Мне нужно свойство 'name'. Ага! В этом экземпляре объекта B уже есть свойство 'name'. Просто верните его - мне все равно, область видимости (т.е. имя А.) "

Итак, в первый раз, когда вы пишете в name, он вставляет его как свойство первого класса в экземпляре, и его больше не волнует, что на AAname - это , все еще там , это просто дальше вниз по цепочке контекста, и интерпретатор JS не продвинется так далеко, пока не найдет то, что искал.

Если бы «name» имело значение по умолчанию в A (как у вас, которое есть «A»), то вы бы увидели это поведение:

B.prototype = new A();
var obj1 = new B();
var obj2 = new B();

obj1.name = "Fred";

alert(obj1.name); // Alerts Fred
alert(obj2.name); // Alerts "A", your original value of A.name

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

Интерпретатор: "Мне нужно получить свойство 'list' и добавить в него элемент. Проверка этого экземпляра B ... nope, не имеет свойства" list ". Следующее в цепочке областей: A. Да, у А есть «список», используйте его. Теперь нажмите на этот список "

Это будет не , если бы вы сделали это:

obj1.list = [];
obj1.push(123);
4 голосов
/ 08 октября 2009

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

Тогда не наследуйте от нового экземпляра A, наследуйте от прототипа A. Это гарантирует, что вы не наследуете состояние, а только наследуете поведение. Это сложная часть наследования прототипов, она также наследует состояние, а не только поведение, как при классическом наследовании ООП. В JavaScript функции конструктора должны использоваться только для настройки состояния экземпляра.

var A = function (list) {
    this.list = list;
    this.name = "A";
};

A.prototype.getName = function () {
    return this.name;
};

var B = function (list) {
    this.list = list;
    this.name = "B";
};

B.prototype = A.prototype;   // Inherit from A's prototype
B.prototype.constructor = B; // Restore constructor object

var b = new B([1, 2, 3]);

// getName() is inherited from A's prototype
print(b.getName()); // B

И, кстати, если вы хотите что-то изменить в A, через B, вы должны сделать это:

B.prototype.name = "obj1";
4 голосов
/ 08 октября 2009
  • Вы говорите, что в этом коде B является реализация А.
  • Тогда вы говорите, что obj1 - это новый экземпляр B и так должен захватить все значений А.
  • Затем вы добавляете элемент к ссылка на местоположение списка в obj2 который в свою очередь добавляет элемент к ссылка на местоположение списка в A (потому что они ссылаются на одно и то же).
  • Затем вы меняете значение имени в obj1.
  • Тогда вы говорите, что obj2 НОВЫЙ экземпляр B и так должен захватить все значений А.

Вы изменили значения списка, указанного в A, но не саму ссылку. имя obj2.name должно быть "A".

3 голосов
/ 08 октября 2009

Когда вы помещаете новый объект в список obj1.list, вы изменяете существующий список в прототипе. Когда вы изменяете имя, вы назначаете свойство для obj1, экземпляр, , а не прототип. Обратите внимание, что то же самое произошло бы, если бы вы сделали:

obj1.list = ["from obj1"]
...
console.log(obj2.list) // <--- Will log [] to console
2 голосов
/ 08 октября 2009

Вы переназначаете имя переменной. Происходит то, что вы переназначаете новую переменную имени для obj1. Ваше объявление имени переопределяет объявление объекта-прототипа (поэтому вы его не видите). Со списком вы изменяете список на месте, не меняя его ссылку.

0 голосов
/ 08 октября 2009

Это происходит потому, что вы не присваиваете значение списку, вы его модифицируете.

Это совершенно разумное поведение, когда вы понимаете, как работает цепочка прототипов. Когда вы ссылаетесь на obj1.list, он сначала проверяет, существует ли «список» в obj1, использует его, если найден, и в противном случае использует тот, который найден в obj1.prototype (который является MyClass.prototype.list).

Итак:

obj1.list.push("test"); // modifies MyClass.prototype.list
obj1.list = ["new"]; // creates a "list" property on obj1
obj1.list.push("test"); // modifies obj1.list, not MyClass.prototype.list
delete obj1.list; // removes the "list" property from obj1
// after the delete, obj1.list will point to the prototype again
obj1.list.push("test"); // again modifies MyClass.prototype.list

Самый важный вывод: «прототипы - это не классы». Прототипы могут довольно хорошо справляться с фальшивыми классами, но они не являются классами, поэтому вам не следует относиться к ним как к таковым.

...